package com.authine.cloudpivot.ext.service.invoice.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapBuilder;
import com.alibaba.cola.dto.RequestContext;
import com.alibaba.cola.dto.SingleResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.authine.cloudpivot.ext.Enum.*;
import com.authine.cloudpivot.ext.applicationservice.InvoiceRecordService;
import com.authine.cloudpivot.ext.constants.ModelConst;
import com.authine.cloudpivot.ext.constants.PlatformConst;
import com.authine.cloudpivot.ext.dto.Summary;
import com.authine.cloudpivot.ext.dto.base.PageList;
import com.authine.cloudpivot.ext.dto.form.InvoiceApplyForm;
import com.authine.cloudpivot.ext.dto.form.InvoiceApplyUpdateForm;
import com.authine.cloudpivot.ext.dto.model.bill.Bill;
import com.authine.cloudpivot.ext.dto.model.golden.GoldenTax;
import com.authine.cloudpivot.ext.dto.model.invoice.*;
import com.authine.cloudpivot.ext.dto.model.settle.SettleDetail;
import com.authine.cloudpivot.ext.dto.model.trade.Trade;
import com.authine.cloudpivot.ext.dto.query.BillQuery;
import com.authine.cloudpivot.ext.dto.query.IdType;
import com.authine.cloudpivot.ext.dto.query.InvoiceDetailQuery;
import com.authine.cloudpivot.ext.dto.query.InvoiceQuery;
import com.authine.cloudpivot.ext.dto.system.OrganizationDto;
import com.authine.cloudpivot.ext.service.invoice.InvoiceApplyService;
import com.authine.cloudpivot.ext.utils.*;
import com.authine.cloudpivot.ext.utils.system.OrgUtils;
import com.authine.cloudpivot.ext.utils.system.RoleUtils;
import com.authine.hermes.app.launcher.service.WorkflowService;
import com.authine.mvp.app.launcher.domain.SelectorFormat;
import com.authine.mvp.app.launcher.domain.utills.ApplicationContextUtils;
import com.authine.mvp.app.launcher.domain.utills.DateUtils;
import com.authine.mvp.app.launcher.dto.bo.response.BO;
import com.authine.mvp.app.launcher.dto.bo.response.BOList;
import com.authine.mvp.app.org.dto.domainmodel.UnitType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StopWatch;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Collections.singletonList;

@Slf4j
public class InvoiceApplyServiceImpl implements InvoiceApplyService {

    protected WorkflowService workflowService = (WorkflowService) ApplicationContextUtils.getBean(WorkflowService.class);
    @Override
    public InvoiceApply loadById(String id) {
        return JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField("*").eq("id", id).queryForPOJO(InvoiceApply.class);
    }

    @Override
    public List<InvoiceApply> loadListByIds(List<String> ids) {
        return JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField("*").in("id", ids).queryForPOJOList(InvoiceApply.class);
    }

    @Override
    public List<ApplyInvoiceDetailDto> viewCanInvoice(IdType idType) {
        log.info("==================获取待申请开票数据{}", JSON.toJSONString(idType));
        //只能处理货物发票申请开票
        if(InvoiceBusinessType.Product.name().equals(idType.getBusinessType().name())){
            String idColumn = "";
            JdbcTemplateUtils jdbcTemplateUtils = JdbcTemplateUtils.builder(ModelConst.T_TRADE).selectField("*");
            if(OriginIDType.Trade == idType.getWays()){
                idColumn = "id";
//                jdbcTemplateUtils.eq("direction", idType.getDirection().name());
            }else if (OriginIDType.CustomerOrder == idType.getWays()){
                idColumn = "customer_order_id";
            }else if (OriginIDType.VendorOrder == idType.getWays()){
                idColumn = "vendor_order_id";
            }
            jdbcTemplateUtils.in(idColumn, idType.getIds());

            List<Trade> tradeList = jdbcTemplateUtils.gt("allowed_invoice_total", BigDecimal.ZERO).queryForPOJOList(Trade.class);
            Assert.notEmpty(tradeList,"不存在可申请开票的交易");

            List<String> tradeIds = tradeList.stream().map(Trade::getId).distinct().collect(Collectors.toList());

            //获取交易的所有退货账单
            List<Bill> refundBills = JdbcTemplateUtils.builder(ModelConst.T_BILL).selectField("*").eq("type", BillType.Refund)
                    .eq("deleted", 0)
                    .in("trade_id", tradeIds)
                    .queryForPOJOList(Bill.class);

            //根据交易记录分组sum已申请数量与金额（排除红冲金额跟数量）
            StringBuffer detailSql = new StringBuffer("select detail.trade_id, detail.tax_rate, sum(detail.quantity - detail.offset_quantity) as quantity, sum(detail.total - detail.offset_total) total")
                    .append(" from ").append(JdbcTemplateUtils.getTableName(ModelConst.T_INVOICE_DETAIL)).append(" as detail ")
                    .append(" left join ").append(JdbcTemplateUtils.getTableName(ModelConst.T_INVOICE_APPLY)).append(" as invoice").append(" on detail.invoice_apply_id = invoice.id")
                    .append(" where detail.deleted = 0 and invoice.business_type = :businessType")
                    .append(" and invoice.statuss != :statuss")
                    .append(" and detail.trade_id in (:tradeIds)")
                    .append(" group by detail.trade_id, detail.tax_rate");
            MapBuilder<String, Object> map = MapBuilder.create(new HashMap<>());
            map.put("businessType", InvoiceBusinessType.Product.name())
                    .put("statuss", InvoiceStatus.Cancel.name())
                    .put("tradeIds", tradeIds);
            List<InvoiceDetail> alreadyInvoiceDetails = JdbcTemplateUtils.queryForPOJOList(detailSql.toString(), map.build(), ModelConst.T_INVOICE_DETAIL, InvoiceDetail.class);

            //获取可申请开票的数据
            List<InvoiceDetailBill> canInvoiceBills = invoiceDetailBillService.getCanInvoiceBill(tradeIds);
            //通过交易ID分组求和
            List<InvoiceDetailBill> canInvoiceBillList = new ArrayList<>();
            canInvoiceBills.stream().collect(Collectors.groupingBy(InvoiceDetailBill::getTrade_id)).forEach((key, values) -> {
                BigDecimal quantity = CalculatorUtils.sum(values, BiFunctionConst.invoiceBillQuantityFun);
                BigDecimal total = CalculatorUtils.sum(values, BiFunctionConst.invoiceBillTotalFun);
                BigDecimal alreadyTotal = CalculatorUtils.sum(values, BiFunctionConst.invoiceProcessBillAlreadyTotalFun);
                canInvoiceBillList.add(InvoiceDetailBill.builder().trade_id(key).quantity(quantity).total(total).already_total(alreadyTotal).build());
            });


            List<ApplyInvoiceDetailDto> returnList = new ArrayList<>();
            for (Trade trade : tradeList){
                //退货的数量和金额
                BigDecimal refundQuantity = BigDecimal.ZERO, refundTotal = BigDecimal.ZERO;
                if (!CollectionUtils.isEmpty(refundBills)) {
                    List<Bill> refundBillList = refundBills.stream().filter(t -> t.getTrade_id().equals(trade.getId())).collect(Collectors.toList());
                    refundQuantity = CalculatorUtils.sum(refundBillList, BiFunctionConst.billQuantityFun);
                    refundTotal = CalculatorUtils.sum(refundBillList, BiFunctionConst.billTotalFun);
                }

                //已申请数量
                BigDecimal alreadyQuantity = BigDecimal.ZERO;
                if (!CollectionUtils.isEmpty(alreadyInvoiceDetails)) {
                    Stream<InvoiceDetail> invoiceDetailEntityStream = alreadyInvoiceDetails.stream().filter(t -> t.getTrade_id().equals(trade.getId()));
                    alreadyQuantity = CalculatorUtils.sum(invoiceDetailEntityStream.collect(Collectors.toList()), BiFunctionConst.invoiceDetailQuantityFun);
                }

                //可申请数量=交易数量-已退数量-已申请数量
                BigDecimal canApplyQuantity = trade.getQuantity().subtract(refundQuantity.add(alreadyQuantity));
                if (canApplyQuantity.compareTo(BigDecimal.ZERO) != 1) {
                    continue;
                }

                List<InvoiceDetailBill> mappingCanInvoiceBills = canInvoiceBillList.stream().filter(t -> t.getTrade_id().equals(trade.getId())).collect(Collectors.toList());

                BigDecimal tradePrice = trade.getPrice();
                //可申请含税金额=可申请数量*含税
                BigDecimal thisTotal = canApplyQuantity.multiply(tradePrice).setScale(2, RoundingMode.HALF_UP);
                if (alreadyQuantity.compareTo(BigDecimal.ZERO) != 0) {
                    BigDecimal alreadyTotal = CalculatorUtils.sum(mappingCanInvoiceBills, BiFunctionConst.invoiceProcessBillAlreadyTotalFun);
                    thisTotal = trade.getTotal().subtract(alreadyTotal);
                    thisTotal = thisTotal.subtract(refundTotal);
                }

                //计算未税金额
                BigDecimal pretax_price = TaxRateUtils.calculateTaxBefore(tradePrice, trade.getTax_rate());
                Summary summary = trade.getSummary();
                ApplyInvoiceDetailDto applyInvoiceDto = ApplyInvoiceDetailDto.builder()
                        .id(trade.getId())
                        .organization_id(trade.getOrganization_id())
                        .direction(trade.getDirection())
                        .business_type(trade.getBusiness_type())
                        .order_goods_id(trade.getOrder_goods_id())
                        .vendor_order_id(trade.getVendor_order_id())
                        .customer_order_id(trade.getCustomer_order_id())
                        .golden_tax(trade.getGolden_tax())
                        .name(summary.getName())
                        .model_spec(summary.getModel() + " " + summary.getSpec())
                        .unit(summary.getUnit())
                        .allowed_invoice_quantity(canApplyQuantity)
                        .allowed_invoice_total(thisTotal)
                        .price(trade.getPrice())
                        .pretax_price(pretax_price)
                        .tax_rate(trade.getTax_rate())
                        .quantity(mappingCanInvoiceBills.get(0).getQuantity())
                        .amount(TaxRateUtils.calculateTaxBefore(mappingCanInvoiceBills.get(0).getTotal(),trade.getTax_rate()))
                        .total(mappingCanInvoiceBills.get(0).getTotal())//本次申请金额（含税）
                        .order_goods_status(trade.getOrder_goods_status())
                        .build();

                //验证开票金额
                BigDecimal verifyTotal = applyInvoiceDto.getPrice().multiply(applyInvoiceDto.getQuantity()).setScale(2, RoundingMode.HALF_UP);
                if (verifyTotal.compareTo(applyInvoiceDto.getTotal()) != 0) {
                    BigDecimal reGrossPrice = applyInvoiceDto.getTotal().divide(applyInvoiceDto.getQuantity(),2, RoundingMode.HALF_UP);
                    applyInvoiceDto.setPretax_price(reGrossPrice)
                            .setPrice(TaxRateUtils.calculateTaxBefore(reGrossPrice, trade.getTax_rate()));
                }
                if (applyInvoiceDto.getQuantity().compareTo(BigDecimal.ZERO) > 0 && applyInvoiceDto.getTotal().compareTo(BigDecimal.ZERO) > 0) {
                    applyInvoiceDto.setErp_po(trade.getErp_po());
                    returnList.add(applyInvoiceDto);
                }

            }
            //排序
            if (idType.getWays() == OriginIDType.Trade) {
                //交易类型则按照传过来的交易ID进行排序
                List<ApplyInvoiceDetailDto> sortAfterProcess = new ArrayList<>();
                for (String id : idType.getIds()) {
                    List<ApplyInvoiceDetailDto> matchInvoiceDetail = returnList.stream().filter(t -> t.getId().equals(id)).collect(Collectors.toList());
                    if (!CollectionUtils.isEmpty(matchInvoiceDetail)) {
                        sortAfterProcess.addAll(matchInvoiceDetail);
                    }
                }
                returnList = sortAfterProcess;
            } else {
                //按照交易ID逆序
                returnList = returnList.stream().sorted(Comparator.comparing(ApplyInvoiceDetailDto::getId).reversed()).collect(Collectors.toList());
            }
            return returnList;
        }
        return null;
    }

    @Override
    public List<String> createProductInvoice(List<InvoiceApplyForm> invoiceApplyFormList) {
        log.info("开始创建货物发票申请单\n{}",JSON.toJSONString(invoiceApplyFormList));
        this.buildInvoiceCreateBefore(invoiceApplyFormList);

        List<InvoiceApplyForm> invoiceApplyProcessesList = new ArrayList<>();
        for (InvoiceApplyForm invoiceApplyForm : invoiceApplyFormList) {
            //平台销售发票才需要拆分，采购发票不需要
            if (PlatformConst.PLATFORM_ORGANIZATION_IDS.contains(invoiceApplyForm.getSale_organization_id().getId())) {
                BigDecimal maxInvoice = MAX_INVOICE;
                //南阳固买增值税专用发票最大申请开票未税金额
                if (invoiceApplyForm.getSale_organization_id().getId().equals(PlatformConst.PLATFORM_ORGANIZATION_IDS.get(1))
                        && InvoiceType.VatSpecial.name().equals(invoiceApplyForm.getType())) {
                    maxInvoice = NANYANG_VAT_SPECIAL_MAX_INVOICE;
                }
                List<InvoiceApplyForm> invoiceApplyProcessess = new ArrayList<>();

                //原申请数据
                InvoiceApplyForm originalInvoiceApplyProcess = new InvoiceApplyForm();
                BeanUtils.copyProperties(invoiceApplyForm, originalInvoiceApplyProcess);

                this.recursionProcessInvoiceDetailProcess(invoiceApplyForm, invoiceApplyProcessess, maxInvoice, originalInvoiceApplyProcess);
                invoiceApplyProcessesList.addAll(invoiceApplyProcessess);
            } else {
                invoiceApplyProcessesList = invoiceApplyFormList;
            }
        }

        //只对南阳固买开的电子普通发票，明细数量大于100的做拆分
        invoiceApplyFormList = this.splitInvoiceDetailProcessCount(invoiceApplyProcessesList);

        boolean isSplit = false;
        Set<String> tradeIdDistinct = new HashSet<>();
        //判断是否为拆分发票
        if (invoiceApplyFormList.size() > 1) {
            for (InvoiceApplyForm process : invoiceApplyFormList) {
                //平台作为采购方不存在分批开票
                if (!PlatformConst.PLATFORM_ORGANIZATION_IDS.contains(process.getSale_organization_id().getId())) {
                    isSplit = false;
                    break;
                }
                //判断是否为同一个交易
                tradeIdDistinct.addAll(process.getApplyInvoiceDetailDtoList().stream().map(ApplyInvoiceDetailDto::getId).distinct().collect(Collectors.toList()));
                isSplit = true;
            }

            //如果不是同一交易则认为不拆分
            isSplit = isSplit && tradeIdDistinct.size() == 1;
        } else {
            //判断是否为同一个交易
            tradeIdDistinct.addAll(invoiceApplyFormList.get(0).getApplyInvoiceDetailDtoList().stream().map(ApplyInvoiceDetailDto::getId).distinct().collect(Collectors.toList()));
        }
        List<String> invoicApplyIds = new ArrayList<>();
        for (InvoiceApplyForm invoiceApplyForm : invoiceApplyFormList) {
            BigDecimal total = BigDecimal.ZERO, priceAmount = BigDecimal.ZERO, taxAmount = BigDecimal.ZERO;
            InvoiceExtendsAttr extendsAttr = invoiceApplyForm.getExtendsAttr();
            if (null == extendsAttr) {
                extendsAttr = InvoiceExtendsAttr.builder().build();
            }

            if (null == extendsAttr.getIfSmallTaxpayer()) {
                extendsAttr.setIfSmallTaxpayer(false);
            }

            //构建发票明细
            List<ApplyInvoiceDetailDto> detailCreations = invoiceApplyForm.getApplyInvoiceDetailDtoList();
            List<InvoiceDetail> details = new ArrayList<>();
            if (!CollectionUtils.isEmpty(detailCreations)) {

                //创建发票的方式 传过的明细参数 发票id
                details = this.createInvoiceDetail(invoiceApplyForm.getInvoiceCreationWays(), detailCreations, extendsAttr.getIfSmallTaxpayer());


                //是否需要审批
                boolean ifApprove = false;
                //审批原因
                StringBuilder reason = new StringBuilder();
                Set<String> taxRate = new HashSet<>();
                for (InvoiceDetail detail : details) {
                    //价税合计、小计、税额
                    total = total.add(detail.getTotal());
                    priceAmount = priceAmount.add(detail.getAmount());
                    taxAmount = taxAmount.add(detail.getTax());

                    //为了兼容10%税率的搜索，需要把税率保存成0.10
                    String taxRateFormat = detail.getTax_rate().toString();
                    if (detail.getTax_rate().compareTo(new BigDecimal("0.1")) == 0) {
                        taxRateFormat = "0.10";
                    }
                    taxRate.add(taxRateFormat);
                    InvoiceDetailExtendsAttr invoiceDetailExtendsAttr = InvoiceDetailExtendsAttr.conversion(detail.getExtends_attr()).orElse(InvoiceDetailExtendsAttr.builder().build());
                    Assert.notNull(invoiceDetailExtendsAttr.getIfApprove(), "是否需要审批数据异常");
                    if (invoiceDetailExtendsAttr.getIfApprove()) {
                        ifApprove = invoiceDetailExtendsAttr.getIfApprove();
                        reason.append(invoiceDetailExtendsAttr.getReason());
                    }
                }

                extendsAttr.setTaxRate(StringUtils.join(taxRate, ",")).setIfApprove(ifApprove).setReason(reason.toString());
            }
            //持久化发票申请单
            InvoiceApply applyEntity = new InvoiceApply();
            BeanUtils.copyProperties(invoiceApplyForm, applyEntity);

            //替换英文括号为中文括号
            String purchaseName = applyEntity.getPurchase_name().replace("(", "（");
            purchaseName = purchaseName.replace(")", "）").trim();

            applyEntity
                    .setTotal(total)
                    .setOffset_total(BigDecimal.ZERO)
                    .setWrite_off_amount(BigDecimal.ZERO)
                    .setWritten_off_amount(BigDecimal.ZERO)
                    .setPrice_amount(priceAmount)
                    .setTax_amount(taxAmount)
                    .setStatuss(InvoiceStatus.New.name())
                    .setExtends_attr(InvoiceExtendsAttr.conversion(extendsAttr).orElse(""))
                    .setTax_rate(extendsAttr.getTaxRate())
                    .setPurchase_name(purchaseName)
                    .setType(invoiceApplyForm.getType())
                    .setApply_time(new Date())
                    .setTrade_order_type(invoiceApplyForm.getTrade_order_type());
            if(invoiceApplyForm.getDirection().equals("Sale")){
                applyEntity.setPlatform_organization_id(applyEntity.getSale_organization_id().getId());
            }else{
                applyEntity.setPlatform_organization_id(applyEntity.getPurchase_organization_id().getId());
            }
            //持久化开票申请单
            SingleResponse<BO> response = BoServiceUtils.createBo(ModelConst.T_INVOICE_APPLY, Collections.singletonList(BoUtils.bo2Map(applyEntity)));
            String invoiceApplyId = response.getData().getBizObjectId();
            Assert.isTrue(StringUtils.isNotBlank(invoiceApplyId), "发票申请单创建失败");
            applyEntity.setId(invoiceApplyId);
            invoicApplyIds.add(invoiceApplyId);

            //持久化开票申请单明细
            for (InvoiceDetail detail : details) {
                detail.setInvoice_apply_id(invoiceApplyId);
            }
            List<Map<String, Object>> params = details.stream().map(BeanUtil::beanToMap).collect(Collectors.toList());
            log.info("发票{}创建的明细\n{}", invoiceApplyId, JSON.toJSONString(params));
            BoServiceUtils.createBo(ModelConst.T_INVOICE_DETAIL, params);
            //将创建的ID赋值到对象上
            details = params.stream().map(t -> BeanUtil.mapToBean(t, InvoiceDetail.class, true)).collect(Collectors.toList());
            //持久化开票
            invoiceDetailBillService.create(details);

            log.info("货物发票开始回写交易");
            List<Map<String, Object>> tradeIds = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_DETAIL).selectField("trade_id")
                    .eq("deleted", 0).eq("invoice_apply_id", invoiceApplyId).queryForList();
            tradeService.writeBackAllowedInvoiceQuantity(tradeIds.stream().map(t -> t.get("trade_id").toString()).collect(Collectors.toList()));
            log.info("交易回写结束");
        }
        if (isSplit) {
            //如果其中一条不需要审批，则说明全部拆分完毕，所以其余的不需要进入审批
            boolean isSplitComplete = false;
            List<InvoiceApply> updateIfApprove = new ArrayList<>();
            List<InvoiceApply> invoiceList = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField("*")
                    .in("id", invoicApplyIds).queryForPOJOList(InvoiceApply.class);

            for (InvoiceApply invoice : invoiceList) {
                InvoiceExtendsAttr invoiceExtendsAttr = InvoiceExtendsAttr.conversion(invoice.getExtends_attr()).get();
                if (null != invoiceExtendsAttr.getIfApprove()) {
                    if ((invoiceExtendsAttr.getIfApprove() != null && invoiceExtendsAttr.getIfApprove()) && !invoiceExtendsAttr.getReason().contains(BalanceMessages)) {
                        updateIfApprove.add(invoice.setExtends_attr(InvoiceExtendsAttr.conversion(invoiceExtendsAttr.setIfApprove(false)).get()));
                    } else if (invoiceExtendsAttr.getReason().equals(BalanceMessages) || (invoiceExtendsAttr.getIfApprove() != null && !invoiceExtendsAttr.getIfApprove())) {
                        isSplitComplete = true;
                    }
                }
            }

            if (isSplitComplete) {
                List<Map<String, Object>> updateMap = updateIfApprove.stream().map(BeanUtil::beanToMap).collect(Collectors.toList());
                BoServiceUtils.updateMainBo( ModelConst.T_INVOICE_APPLY, updateMap);
            }
        }
        return invoicApplyIds;
    }

    /**
     * 创建货物发票申请单前的验证
     * 1、验证发票发现唯一
     * 2、交易的方向与开票的类型一致
     * 3、代购和非代购不能同时申请
     * 4、税率必须唯一
     * @param invoiceApplyFormList
     */
    public void buildInvoiceCreateBefore(List<InvoiceApplyForm> invoiceApplyFormList){
        for (InvoiceApplyForm invoiceApplyForm : invoiceApplyFormList) {
            Assert.isTrue(invoiceApplyForm!=null, "申请开票对象为空");
            Assert.isTrue(invoiceApplyForm.getApplyInvoiceDetailDtoList() != null && invoiceApplyForm.getApplyInvoiceDetailDtoList().size()>0, "申请开票对象为空");

            List<String> tradeIds = invoiceApplyForm.getApplyInvoiceDetailDtoList().stream().map(ApplyInvoiceDetailDto::getId).distinct().collect(Collectors.toList());
            Assert.isTrue(!CollectionUtils.isEmpty(tradeIds), "交易记录ID不能为空");
            List<Trade> tradeList = JdbcTemplateUtils.builder(ModelConst.T_TRADE).selectField("*")
                    .in("id", tradeIds)
                    .queryForPOJOList(Trade.class);
            //验证数据合法性
            Assert.isTrue(!CollectionUtils.isEmpty(tradeList), "未获取到相应交易记录");
            List<String> orgIds = tradeList.stream().map(t->t.getOrganization_id().getId()).distinct().collect(Collectors.toList());
            Assert.isTrue(orgIds.size()==1, "不同组织的交易不能一起申请开票");

            OrganizationDto organizationDto = OrgUtils.getOrgByOrgId(orgIds.get(0));
            Assert.notNull(organizationDto, "结算组织不存在！");
            Assert.isTrue("Sale".equals(invoiceApplyForm.getDirection()) || "Purchase".equals(invoiceApplyForm.getDirection()), "发票的开票类型异常");
            //设置发票申请单的购买方和销售方
            if ("Sale".equals(invoiceApplyForm.getDirection())) {
                Assert.isTrue(StringUtils.isNotBlank(organizationDto.getCredit_identifier()), "采购组织社会信用代码为空");
                Assert.isTrue(organizationDto.getCredit_identifier().trim().length() == 18, "社会信用代码必须为18位");
                invoiceApplyForm.setBusiness_type(InvoiceBusinessType.Product.name())
                        .setApply_member_id(RequestContext.getCorpLoginId())
                        .setApply_member_name(RequestContext.getLoginRealName())
                        .setPurchase_organization_id(tradeList.get(0).getOrganization_id())//selectorFormat
                        .setPurchase_organization_name(tradeList.get(0).getOrganization_id().getName())//selectorFormat.getName()
                        .setSale_organization_id(tradeList.get(0).getPlatform_organization_id())
                        .setSale_organization_name(tradeList.get(0).getPlatform_organization_name());
            } else if("Purchase".equals(invoiceApplyForm.getDirection())) {
                //采购发票开票申请
                //判断是否是小规模纳税人
                InvoiceExtendsAttr extendsAttr = invoiceApplyForm.getExtendsAttr();
                if (null == extendsAttr) {
                    extendsAttr = InvoiceExtendsAttr.builder().build();
                }
                boolean ifSmallTaxpayer = organizationDto.getSmall_scale();
                extendsAttr.setIfSmallTaxpayer(ifSmallTaxpayer);
                invoiceApplyForm.setExtendsAttr(extendsAttr);
                invoiceApplyForm.setBusiness_type(InvoiceBusinessType.Product.name())
                        .setApply_member_id(RequestContext.getCorpLoginId())
                        .setApply_member_name(RequestContext.getLoginRealName())
                        .setPurchase_organization_id(tradeList.get(0).getPlatform_organization_id())
                        .setPurchase_organization_name(tradeList.get(0).getPlatform_organization_name())
                        .setSale_organization_id(tradeList.get(0).getOrganization_id())
                        .setSale_organization_name(tradeList.get(0).getOrganization_id().getName());
            }
            invoiceApplyForm.setTrade_order_type(tradeList.get(0).getBusiness_type());
            //判断交易方向是否一致
            Set<String> tradeDirectionSet = tradeList.stream().map(Trade::getDirection).collect(Collectors.toSet());
            Assert.isTrue(tradeDirectionSet.size() == 1, "发票方向不唯一");
            Assert.isTrue(tradeDirectionSet.contains(invoiceApplyForm.getDirection()), "交易方向与需要开票的类型不一致");
            //判断业务类型是否唯一
            Set<String> tradeOrderTypeSet = tradeList.stream().map(Trade::getBusiness_type).collect(Collectors.toSet());
            Assert.isTrue(!CollectionUtils.isEmpty(tradeOrderTypeSet) && tradeOrderTypeSet.size() == 1, "创建发票时，代购和非代购不能同时申请");
            //判断税率是否唯一
            Set<BigDecimal> taxRateSet = tradeList.stream().map(Trade::getTax_rate).collect(Collectors.toSet());
            Assert.isTrue(taxRateSet.size() == 1, "税率必须唯一");
            //交易平台组织唯一
            List<String> platformOrganizationName = tradeList.stream().map(Trade::getPlatform_organization_name).distinct().collect(Collectors.toList());
            Assert.isTrue(platformOrganizationName.size() == 1, "获取交易中平台组织ID异常");
            //筛选出金税编码为空的交易
            List<Trade> tradeGoldenTaxNullEntityList = tradeList.stream().filter(t -> "".equals(t.getGolden_tax()) || t.getGolden_tax() == null).collect(Collectors.toList());
            Set<String> orderGoodsIdSet = tradeGoldenTaxNullEntityList.stream().map(Trade::getOrder_goods_id).collect(Collectors.toSet());

            if (PlatformConst.PLATFORM_ORGANIZATION_IDS.contains(invoiceApplyForm.getSale_organization_id().getId())) {
                Assert.isTrue(CollectionUtils.isEmpty(tradeGoldenTaxNullEntityList), "商品编号为：" + orderGoodsIdSet + "金税编码不能为空");
            }
            //通过金税编码判断是否是成品油和非成品油
            Set<Integer> tradeGoldenTax = new HashSet<>();
            StringBuilder str = new StringBuilder();
            for (Trade tradeEntity : tradeList) {
                if (PlatformConst.GOLDEN_TAX_OIL.contains(tradeEntity.getGolden_tax())) {
                    tradeGoldenTax.add(1);
                    str.append(tradeEntity.getOrder_goods_id()).append(",");
                } else {
                    tradeGoldenTax.add(2);
                }
            }
            if (PlatformConst.PLATFORM_ORGANIZATION_IDS.contains(invoiceApplyForm.getPurchase_organization_id().getId())) {
                Assert.isTrue(tradeGoldenTax.size() == 1, "订单商品ID" + str + "产品名称涉及成品油，请确认商品是否为成品油，成品油与非成品油要分开申请开票");
            }

            //验证申请开票数量是否大于可申请数量
            for (Trade trade : tradeList) {
                List<ApplyInvoiceDetailDto> invoiceDetailDtos = invoiceApplyForm.getApplyInvoiceDetailDtoList().stream().filter(t -> t.getId().equals(trade.getId())).distinct().collect(Collectors.toList());
                BigDecimal sum = BigDecimal.ZERO;
                for (ApplyInvoiceDetailDto item : invoiceDetailDtos) {
                    sum = sum.add(item.getQuantity());
                }
                Assert.isTrue(trade.getAllowed_invoice_quantity().compareTo(sum) >= 0,
                        "申请开票数量" + sum + "不能大于可申请数量" + trade.getAllowed_invoice_quantity().setScale(4, RoundingMode.HALF_UP));
            }
            //创建前的校验
            this.verifyCreate(invoiceApplyForm);

        }
    }
    /**
     * 构建开票的发票明细 暂不持久化，先持久化开票申请单后获取ID才能持久化明细
     * @param applyParams  通过交易生成的开票明细
     * @param ifSmallTaxpayer  是否小规模纳税人
     */
    public List<InvoiceDetail> createInvoiceDetail(OriginIDType originIDType,List<ApplyInvoiceDetailDto> applyParams, boolean ifSmallTaxpayer){
        for (ApplyInvoiceDetailDto applyInvoiceDetailDto : applyParams){
            Assert.notNull(applyInvoiceDetailDto.getQuantity(), "编号【" + applyInvoiceDetailDto.getId() + "】数量不可小于0");
            Assert.notNull(applyInvoiceDetailDto.getTotal(), "编号【" + applyInvoiceDetailDto.getId() + "】数量不可小于0");
        }
        IdType idType = IdType.builder()
                .ways(originIDType)
                .ids(applyParams.stream().map(ApplyInvoiceDetailDto::getId).collect(Collectors.toList()))
                .businessType(InvoiceBusinessType.Product)
                .build();
        List<ApplyInvoiceDetailDto> canInvoiceTrade = this.viewCanInvoice(idType);
        List<InvoiceDetail> persist = new ArrayList<>();
        int i = 0;

        for (ApplyInvoiceDetailDto process : canInvoiceTrade) {
            StringBuilder reason = new StringBuilder();
            boolean ifApprove = false;
            //赋值可编辑项
            List<ApplyInvoiceDetailDto> listTradeProcess = applyParams.stream().filter(t -> t.getId().equals(process.getId())).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(listTradeProcess)) {
                //取交易，对比名称
                Trade tradeEntity = JdbcTemplateUtils.builder(ModelConst.T_TRADE).selectField("*")
                        .eq("id", process.getId()).queryForPOJO(Trade.class);
                Assert.isTrue(!ObjectUtils.isEmpty(tradeEntity), "未获取到相应交易");
                Summary summary = tradeEntity.getSummary();
                String modelSpec = summary.getModel() + " " + summary.getSpec();

                if (null != listTradeProcess.get(0).getName()) {
                    if (!listTradeProcess.get(0).getName().trim().equals(summary.getName().trim())) {
                        reason.append("【商品名称由：").append(summary.getName().trim()).append("，更改为：").append(listTradeProcess.get(0).getName().trim()).append("。】");
                        ifApprove = true;
                    }
                    process.setName(listTradeProcess.get(0).getName());
                }
                if (null != listTradeProcess.get(0).getModel_spec()) {
                    if (!listTradeProcess.get(0).getModel_spec().trim().equals(modelSpec.trim())) {
                        reason.append("【商品型号由：").append(modelSpec.trim()).append("，更改为：").append(listTradeProcess.get(0).getModel_spec().trim()).append("。】");
                        ifApprove = true;
                    }
                    process.setModel_spec(listTradeProcess.get(0).getModel_spec());
                }
            }

            String errorMsg = "交易编号【" + process.getId() + "】";
            Assert.isTrue(process.getAllowed_invoice_quantity().compareTo(BigDecimal.ZERO) == 1, errorMsg + "不存在可申请金额");

            ApplyInvoiceDetailDto applyParam = applyParams.stream().filter(t -> t.getId().equals(process.getId())).collect(Collectors.toList()).get(0);

            BigDecimal applyQuantity = applyParam.getQuantity(),
                    applyTotal = applyParam.getTotal(),
                    taxRate = applyParam.getTax_rate();
            Assert.isTrue(null != applyQuantity && applyQuantity.compareTo(BigDecimal.ZERO) == 1, errorMsg + "本次申请数量不能为空或小于等于0");
            Assert.isTrue(null != applyTotal && applyTotal.compareTo(BigDecimal.ZERO) == 1, errorMsg + "本次申请金额不能为空或小于等于0");
            Assert.isTrue(null != taxRate && taxRate.compareTo(BigDecimal.ZERO) != -1, errorMsg + "申请税率不能为空或小于0");

            Assert.isTrue(applyQuantity.compareTo(process.getAllowed_invoice_quantity()) != 1, errorMsg + "本次申请数量" + applyQuantity + "不能大于可申请数量" + process.getAllowed_invoice_quantity());

            BigDecimal balanceBefore = process.getTotal();
            //数量拆分申请，需要审批
            if (process.getQuantity().compareTo(applyQuantity) != 0) {
                reason.append("申票数量与已对账可申票数量不等：【申票数量").append(applyQuantity).append("】【已对账可申票数量").append(process.getQuantity()).append("】");
                balanceBefore = applyQuantity.multiply(process.getPrice()).setScale(2, RoundingMode.HALF_UP);
                ifApprove = true;
            }

            //必须为false才可继续验证,是否需要审批，否则属于正常小规模补差调整含税金额直接跳过审批
            boolean smallTaxpayerJump = false;
            if (ifSmallTaxpayer && process.getTax_rate().compareTo(taxRate) != 0 && !ifApprove) {
                //小规模 && 更改了税率 && 没有其他审批条件
                BigDecimal verifySmallAmount = TaxRateUtils.calculateTaxBefore(process.getTotal(), process.getTax_rate()).setScale(2, RoundingMode.HALF_UP);
                BigDecimal verifySmallTotal = TaxRateUtils.calculateTaxAfter(verifySmallAmount, taxRate).setScale(2, RoundingMode.HALF_UP);
                if (applyTotal.compareTo(verifySmallTotal) == 0) {
                    smallTaxpayerJump = true;
                }
            }

            if (balanceBefore.compareTo(applyTotal) != 0) {
                //补差后含税单价需要根据总金额与数量反算
                BigDecimal balanceAfterGrossPrice = BigDecimalUtils.div(applyTotal, applyQuantity);
                process.setPrice(balanceAfterGrossPrice);
                if (!smallTaxpayerJump) {
                    BigDecimal balance = applyTotal.subtract(balanceBefore).setScale(2, RoundingMode.HALF_UP);
                    if (balance.abs().compareTo(BigDecimal.ONE) == 1) {
                        reason.append(BalanceMessages);
                        ifApprove = true;
                    }
                }
            }

            InvoiceDetailExtendsAttr extendsAttr = InvoiceDetailExtendsAttr.builder()
                    .orderGoodsId(process.getOrder_goods_id())
                    .vendorOrderId(process.getVendor_order_id())
                    .customerOrderId(process.getCustomer_order_id())
                    .ifApprove(ifApprove)
                    .reason(reason.toString())
                    .build();

            InvoiceDetail invoiceDetail = InvoiceDetail.builder()
                    //.invoice_apply_id(invoiceApplyId)
                    .trade_id(process.getId())
                    .golden_tax(process.getGolden_tax())
                    .name(process.getName().trim())
                    .model_spec(process.getModel_spec().trim())
                    .unit(process.getUnit().trim())
                    .offset_quantity(BigDecimal.ZERO)
                    .offset_total(BigDecimal.ZERO)
                    .quantity(applyQuantity)
                    .gross_price(process.getPrice())
                    .tax_rate(taxRate)
                    .total(applyTotal)
                    .extends_attr(InvoiceDetailExtendsAttr.conversion(extendsAttr).get())
                    .build();

            //根据含税计算未税
            BigDecimal amount = TaxRateUtils.calculateTaxBefore(invoiceDetail.getTotal(), invoiceDetail.getTax_rate()).setScale(6, RoundingMode.HALF_UP);
            //税额
            BigDecimal tax = invoiceDetail.getTotal().subtract(amount);
            //计算未税单价
            BigDecimal price = BigDecimalUtils.div(amount, invoiceDetail.getQuantity());
            invoiceDetail.setAmount(amount).setTax(tax).setPrice(price);

            persist.add(invoiceDetail);
            i++;
        }
        return persist;

    }


    @Override
    public void submit(List<String> invoiceIds) {
        List<InvoiceApply> invoiceApplyList = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField(" * ")
                .in("id",invoiceIds)
                .queryForPOJOList(InvoiceApply.class);
        Assert.isTrue(!CollectionUtils.isEmpty(invoiceApplyList), "获取发票申请单数据失败");

        invoiceApplyList = invoiceApplyList.stream().filter(t -> InvoiceStatus.New.name().equals(t.getStatuss()) || InvoiceStatus.Returned.name().equals(t.getStatuss()))
                .collect(Collectors.toList());
        for (InvoiceApply invoiceApply : invoiceApplyList){

            List<InvoiceDetail> details = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_DETAIL).selectField(" * ")
                    .eq("invoice_apply_id", invoiceApply.getId())
                    .queryForPOJOList(InvoiceDetail.class);

            Assert.isTrue(!CollectionUtils.isEmpty(details), "发票申请单号【" + invoiceApply.getId() + "】,不存在明细，请添加明细");

            //判断当前操作人是否是供应商
            boolean isVendor = RoleUtils.existByUserIdAndRoleName(RequestContext.getCorpLoginId(), PlatformConst.ROLE_VENDOR_ADMIN);
            //如果是平台采购申请开票，是供应商自己申请，则直接进入审批流
            if (InvoiceBusinessType.Product.name().equals(invoiceApply.getBusiness_type())
                    && PlatformConst.PLATFORM_ORGANIZATION_IDS.contains(invoiceApply.getPurchase_organization_id().getId())
                    && isVendor){
                log.info("发起开票流程invoice_approve，开票申请id::{}",invoiceApply.getId());
                workflowService.startWorkflowInstance("invoice_approve",invoiceApply.getId(), RequestContext.getCorpLoginId(),invoiceApply.getPurchase_organization_id().getId(),true,"","","");
                JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY)
                        .putFieldAndVal("statuss", InvoiceStatus.Approving.name())
                        .putFieldAndVal("apply_time", DateUtils.getCurrentDate())
                        .eq("id", invoiceApply.getId())
                        .update();
                continue;
            }else{
                InvoiceExtendsAttr invoiceExtendsAttr = InvoiceExtendsAttr.conversion(invoiceApply.getExtends_attr()).orElse(InvoiceExtendsAttr.builder().build());
                if(InvoiceBusinessType.Product.name().equals(invoiceApply.getBusiness_type())
                        && null != invoiceExtendsAttr.getIfApprove() && invoiceExtendsAttr.getIfApprove()) {
                    log.info("发起开票流程invoice_approve，开票申请id::{}",invoiceApply.getId());
                    workflowService.startWorkflowInstance("invoice_approve",invoiceApply.getId(), RequestContext.getCorpLoginId(),invoiceApply.getPurchase_organization_id().getId(),true,"","","");
                    JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY)
                            .putFieldAndVal("statuss", InvoiceStatus.Approving.name())
                            .putFieldAndVal("apply_time", DateUtils.getCurrentDate())
                            .eq("id", invoiceApply.getId())
                            .update();
                    continue;
                }
            }
            invoiceApply.setStatuss(InvoiceStatus.DrawerConfirming.name());//不能删除，否则无法创建金税开票信息
            JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY)
                    .putFieldAndVal("statuss", invoiceApply.getStatuss())
                    .putFieldAndVal("apply_time", DateUtils.getCurrentDate())
                    .putFieldAndVal("golden_tax_id", goldenTaxService.generateGoldenTax(invoiceApply))
                    .eq("id", invoiceApply.getId())
                    .update();
        }
    }

    @Override
    public InvoiceApply confirmInvoice(MakeInvoice makeInvoice) {
        InvoiceApply findInvoice = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField(" * ")
                .eq("id", makeInvoice.getId()).eq("deleted",0).queryForPOJO(InvoiceApply.class);

        Assert.notNull(findInvoice, "确认开票的申请单不存在");
        Assert.isTrue(InvoiceStatus.DrawerConfirming.name().equals(findInvoice.getStatuss()), "状态不为待开票状态，不能开票");

        //确认开票
        Map<String, Object> saleInfoMap = new HashMap<>();
        if(InvoiceBusinessType.Product.name().equals(findInvoice.getBusiness_type())
                && PlatformConst.PLATFORM_ORGANIZATION_IDS.contains(findInvoice.getPurchase_organization_id().getId())){
            Assert.notNull(makeInvoice.getSale_name(), "抬头不能为空");
            Assert.notNull(makeInvoice.getSale_number(), "税号不能为空");
            Assert.notNull(makeInvoice.getSale_address(), "开票地址不能为空");
            Assert.notNull(makeInvoice.getSale_phone(), "电话不能为空");
            Assert.notNull(makeInvoice.getSale_bank(), "开户行不能为空");
            Assert.notNull(makeInvoice.getSale_account(), "账号不能为空");
            saleInfoMap.put("sale_name", makeInvoice.getSale_name());
            saleInfoMap.put("sale_number", makeInvoice.getSale_number());
            saleInfoMap.put("sale_address", makeInvoice.getSale_address());
            saleInfoMap.put("sale_phone", makeInvoice.getSale_phone());
            saleInfoMap.put("sale_bank", makeInvoice.getSale_bank());
            saleInfoMap.put("sale_account", makeInvoice.getSale_account());

        }

        InvoiceRecord record = invoiceRecordService.record(makeInvoice.getId(), makeInvoice.getInvoiceApplyRecordList());
        Map<String,Object> map = MapBuilder.create(new HashMap<String, Object>())
                .put("number", record.getNumber())
                .put("code", record.getCode())
                .put("drawer",makeInvoice.getDrawer())
                .put("price_amount",record.getPretax_amount())
                .put("tax_amount",record.getTax_payable())
                .put("date", makeInvoice.getDate())
                .put("drawer_member_id", makeInvoice.getDrawer_member_id())
                .put("drawer_member_name", makeInvoice.getDrawer_member_name())
                .put("statuss", InvoiceStatus.ReceiverConfirming.name())
                .put("write_off_amount", record.getPretax_amount().add(record.getTax_payable()))
                .put("order_type", makeInvoice.getOrderType())
                .put("number_attachments", record.getNumber_attachments())
                .put("attachments_json", makeInvoice.getAttachmentsJson() == null || makeInvoice.getAttachmentsJson().size()==0 ? "[]":makeInvoice.getAttachmentsJson())
                .putAll(saleInfoMap).build();
        JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY)
                .putFieldAndVal(map)
                .eq("id", findInvoice.getId())
                .update();
        if (findInvoice.getGolden_tax_id() != null) {
            JdbcTemplateUtils.builder(ModelConst.T_GOLDEN_TAX)
                    .putFieldAndVal("statuss", GoldenTaxStatus.Done.name())
                    .eq("id", findInvoice.getGolden_tax_id())
                    .update();
        }
        this.writeBackTradeInvoiceNumber(Collections.singletonList(findInvoice.getId()));
        return findInvoice;
    }

    @Override
    public void receiveInvoice(ConfirmInvoice confirmInvoice) {
        InvoiceApply invoiceApply = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField(" * ")
                .eq("id", confirmInvoice.getId())
                .queryForPOJO(InvoiceApply.class);
        Assert.notNull(invoiceApply, "未查对应的发票单【" + confirmInvoice.getId() + "】");
        Assert.isTrue(InvoiceStatus.ReceiverConfirming.name().equals(invoiceApply.getStatuss()), "状态不为待收票确认，不能收票");

        Map<String, Object> updateMap = MapBuilder.create(new HashMap<String, Object>()).put("id", invoiceApply.getId()).build();
        if (!CollectionUtils.isEmpty(confirmInvoice.getInvoiceApplyRecordList())) {
            InvoiceRecordService invoiceRecordService = new InvoiceRecordService();
            InvoiceRecord record = invoiceRecordService.record(confirmInvoice.getId(), confirmInvoice.getInvoiceApplyRecordList());

            updateMap.put("price_amount", record.getPretax_amount());
            updateMap.put("tax_amount", record.getTax_payable());
            updateMap.put("number", record.getNumber());
            updateMap.put("code", record.getCode());
            updateMap.put("number_attachements", record.getNumber_attachments());
        }
        updateMap.put("confirm_time", confirmInvoice.getConfirm_time());
        updateMap.put("recipient_member_id", RequestContext.getCorpLoginId());
        updateMap.put("recipient_member_name", RequestContext.getLoginRealName());
        updateMap.put("statuss", InvoiceStatus.Done.name());

        BoServiceUtils.updateMainBo( ModelConst.T_INVOICE_APPLY, updateMap);
        if (InvoiceBusinessType.Product.name().equals(invoiceApply.getBusiness_type())|| InvoiceBusinessType.RedProduct.name().equals(invoiceApply.getBusiness_type())) {

            List<String> tradeIds = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_DETAIL_BILL)
                    .selectField(" DISTINCT trade_id ").eq("invoice_apply_id", invoiceApply.getId())
                    .queryForList(String.class);
            tradeService.writeBackAllowedInvoiceQuantity(tradeIds);
        }

        //非寄送发票
        if (!confirmInvoice.isIfSend()) {
            //调用金蝶接口数据
            kingDeeFinanceService.receiveInvoiceInvoke(invoiceApply);
        }
    }

    @Override
    public void invoiceRollback(List<String> invoiceIds) {
        List<InvoiceApply> invoiceApplyList = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField("*")
                .in("id", invoiceIds)
                .queryForPOJOList(InvoiceApply.class);
        Assert.isTrue(!CollectionUtils.isEmpty(invoiceApplyList),"未查到要操作的发票");
        Map<String,Object> map = MapBuilder.create(new HashMap<String, Object>()).build();
        for (InvoiceApply invoiceApply:invoiceApplyList){
            Assert.isTrue(!settleService.isInvoiceHaveSettled(invoiceIds), "【" + invoiceApply.getId() + "】该发票已结算不能回退操作");
            Assert.isTrue(!invoiceApply.getDeleted(), "不可对已被删除的发票进行操作");
            Assert.isTrue(InvoiceStatus.Done.name().equals(invoiceApply.getStatuss()), "只有已开票状态才可操作");
            Assert.isTrue(!InvoiceBusinessType.RedService.name().equals(invoiceApply.getBusiness_type()) && !InvoiceBusinessType.RedProduct.name().equals(invoiceApply.getBusiness_type()), "红字发票不可撤销");
            Assert.isTrue(invoiceApply.getOffset_total().compareTo(BigDecimal.ZERO) == 0, "该蓝票已被红冲不可撤销");

            invoiceApply.setStatuss(InvoiceStatus.ReceiverConfirming.name());
            //SelectorFormat selectorFormat = JSON.parseObject(invoiceApply.getSale_organization_id(), SelectorFormat.class);
            if (PlatformConst.PLATFORM_ORGANIZATION_IDS.contains(invoiceApply.getSale_organization_id().getId())) {
                invoiceApply.setStatuss(InvoiceStatus.DrawerConfirming.name());
            }
            map.clear();
            map.put("id",invoiceApply.getId());
            map.put("statuss", invoiceApply.getStatuss());
            BoServiceUtils.updateMainBo( ModelConst.T_INVOICE_APPLY,map);
            //回写交易可申请开票数量、金额
            List<String> invoiceDetailIds = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_DETAIL).selectField("trade_id")
                    .eq("invoice_apply_id", invoiceApply.getId())
                    .queryForList(String.class);

            tradeService.writeBackAllowedInvoiceQuantity(invoiceDetailIds);

            //修改金税表状态为待开票
            if (invoiceApply.getStatuss().equals( InvoiceStatus.DrawerConfirming.name()) && invoiceApply.getGolden_tax_id() != null) {
                Map<String, Object> param = MapBuilder.create(new HashMap<String, Object>())
                        .put("id", invoiceApply.getId())
                        .put("statuss", GoldenTaxStatus.DrawerConfirming.name())
                        .build();
                BoServiceUtils.updateMainBo( ModelConst.T_GOLDEN_TAX, param);
            }
        }
        this.writeBackTradeInvoiceNumber(invoiceIds);
        //金蝶接口调用
        kingDeeFinanceService.doInvalidInvoice(invoiceApplyList);
    }

    @Override
    public void offsetInvoice(OffsetInvoice offsetInvoice) {
        InvoiceApply invoiceApply = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField("*")
                .eq("id", offsetInvoice.getId()).queryForPOJO(InvoiceApply.class);
        InvoiceApply offsetInvoiceResult;
        Assert.notNull(invoiceApply, "要红冲的发票单不存在");

        Assert.isTrue(invoiceApply.getOffset_total().compareTo(invoiceApply.getTotal())<1, "发票已全部红冲");
        Assert.isTrue(InvoiceBusinessType.Product.name().equals(invoiceApply.getBusiness_type())
                        || InvoiceBusinessType.Service.name().equals(invoiceApply.getBusiness_type()),
                    "只能对服务费、商品的蓝字发票进行冲抵");
        if(InvoiceBusinessType.Product.name().equals(invoiceApply.getBusiness_type())){
            offsetInvoiceResult = this.goodsOffset(offsetInvoice, invoiceApply);
        }else{
            offsetInvoiceResult = invoiceDetailBillService.offsetService(invoiceApply,offsetInvoice.getOffsetInvoiceDetails());
        }
        if (!InvoiceStatus.Done.name().equals(offsetInvoiceResult.getStatuss())) {
            //确认开票
            MakeInvoice makeInvoice = MakeInvoice.builder()
                    .id(offsetInvoiceResult.getId())
                    .date(offsetInvoice.getInvoice_time())
                    .drawer(offsetInvoice.getDrawer())
                    .drawer_member_id(RequestContext.getCorpLoginId())
                    .drawer_member_name(RequestContext.getLoginRealName())
                    .invoiceApplyRecordList(offsetInvoice.getInvoiceApplyRecordList())
                    .attachmentsJson(offsetInvoice.getAttachments_json())
                    .build();
            confirmInvoice(makeInvoice);
            //确认收票
            ConfirmInvoice confirmInvoice = ConfirmInvoice.builder()
                    .id(offsetInvoiceResult.getId())
                    .confirm_time(offsetInvoice.getInvoice_time())
                    .build();
            receiveInvoice(confirmInvoice);
            //回写相关数据
            //获取发票红冲单ID回写到拓展字段中
            InvoiceExtendsAttr extendsAttr = InvoiceExtendsAttr.conversion(invoiceApply.getExtends_attr()).orElse(new InvoiceExtendsAttr());
            String sql = "select group_concat(distinct id order by id desc) as offsetId from "+JdbcTemplateUtils.getTableName(ModelConst.T_INVOICE_APPLY)
                    +" where business_type='RedProduct' and deleted = 0 and statuss='Done' and offset_id='"+invoiceApply.getId()+"' ";
            Map<String, Object> offsetIds = jdbcTemplate.queryForMap(sql, new HashMap<>());
            extendsAttr.setOffsetIds(offsetIds.get("offsetId").toString());
            //计算发票已红冲金额回写
            sql = "select ifnull(sum(offset_total), 0) offsetTotal from "+JdbcTemplateUtils.getTableName(ModelConst.T_INVOICE_APPLY)
                    +" where  deleted = 0 and offset_id='"+invoiceApply.getId()+"'";
            Map<String, Object> offsetTotalSum = jdbcTemplate.queryForMap(sql, new HashMap<>());
            invoiceApply.setOffset_total(new BigDecimal(offsetTotalSum.get("offsetTotal").toString()));
            invoiceApply.setExtends_attr(InvoiceExtendsAttr.conversion(extendsAttr).get());

            Map<String, Object> param = new HashMap<>();
            param.put("id",invoiceApply.getId());
            param.put("offset_total", invoiceApply.getOffset_total());
            param.put("extends_attr", invoiceApply.getExtends_attr());
            BoServiceUtils.updateMainBo( ModelConst.T_INVOICE_APPLY, param);

            //回写蓝票的可核销金额和已核销金额
            this.writeBackInvoiceWriteOffAmountByInvoiceApply(singletonList(invoiceApply.getId()));

            //全部红冲时，要回写交易发票号码
            if (invoiceApply.getOffset_total().compareTo(invoiceApply.getTotal())==0) {
                this.writeBackTradeInvoiceNumber(Collections.singleton(invoiceApply.getId()));
            }
        }
        //生成金蝶红冲凭证
        if (PlatformConst.PLATFORM_ORGANIZATION_IDS.contains(invoiceApply.getPurchase_organization_id().getId()) && InvoiceBusinessType.valueOf(invoiceApply.getBusiness_type()) == InvoiceBusinessType.RedProduct) {
            invoiceVoucherService.createRedInvoiceVoucher(recordingVoucherPmUtil.createOrUpdateRedInvoiceVoucher(invoiceApply,null));
        }
    }

    @Override
    public List<String> createServiceInvoice(List<InvoiceApplyForm> invoiceApplyFormList) {
        List<String> serviceInvoiceIds = new ArrayList<>();
        //创建服务费发票
        invoiceApplyFormList.forEach(invoiceApplyForm -> {
            //账单维度的开票明细 明细ID记录的账单ID
            List<ApplyInvoiceDetailDto> billInvoiceDetailList = invoiceApplyForm.getApplyInvoiceDetailDtoList();
            //创建服务费发票明细的时候ID记录的是服务费账单ID
            List<String> serviceBillIds = billInvoiceDetailList.stream().map(ApplyInvoiceDetailDto::getId).distinct().collect(Collectors.toList());
            List<Bill> billList = JdbcTemplateUtils.builder(ModelConst.T_BILL).selectField(" * ")
                    .in("id", serviceBillIds).queryForPOJOList(Bill.class);
            Assert.notEmpty(billList, "申请开票的服务费账单不存在！");
            List<String> tradeOrderTypeList = billList.stream().map(Bill::getBusiness_type).distinct().collect(Collectors.toList());
            Assert.isTrue(!CollectionUtils.isEmpty(tradeOrderTypeList) && tradeOrderTypeList.size() == 1, "代购和非代购不能一起申请开票");
            invoiceApplyForm.setTrade_order_type(tradeOrderTypeList.get(0));
            //发票验证
            verifyCreate(invoiceApplyForm);

            if(!CollectionUtils.isEmpty(billInvoiceDetailList)){
                List<ApplyInvoiceDetailDto> shanghaiPlatformInvoiceDetailProcess = new ArrayList<>(), henanPlatformInvoiceDetailProcess = new ArrayList<>();

                //通过账单获取交易维度可开票明细 明细ID记录的是交易ID
                List<String> billIds = billInvoiceDetailList.stream().map(ApplyInvoiceDetailDto::getId).distinct().collect(Collectors.toList());
                //此处ApplyInvoiceDetailDto的ID开始记录为tradeId
                List<ApplyInvoiceDetailDto> invoiceDetailProcessList = this.getCanApplyServiceInvoice(billIds);
                Assert.isTrue(!CollectionUtils.isEmpty(invoiceDetailProcessList), "未获取到开票申请明细");

                //从交易记录获取平台组织id
                Trade tradeEntity = JdbcTemplateUtils.builder(ModelConst.T_TRADE).selectField("*")
                        .eq("id", invoiceDetailProcessList.get(0).getId())
                        .queryForPOJO(Trade.class);
                Assert.notNull(tradeEntity, "未获取到开票申请明细对应的交易");

                //根据客户对应平台组织分类
                for (ApplyInvoiceDetailDto detailProcess : invoiceDetailProcessList) {
                    if (PlatformConst.PLATFORM_ORGANIZATION_IDS.get(1).equals(tradeEntity.getPlatform_organization_id().getId())) {
                        henanPlatformInvoiceDetailProcess.add(detailProcess);
                    } else {
                        shanghaiPlatformInvoiceDetailProcess.add(detailProcess);
                    }
                }
                if (!CollectionUtils.isEmpty(henanPlatformInvoiceDetailProcess)) {
                    invoiceApplyForm.setApplyInvoiceDetailDtoList(henanPlatformInvoiceDetailProcess);
                    serviceInvoiceIds.addAll(this.doCreateService(invoiceApplyForm));
                } else {
                    invoiceApplyForm.setApplyInvoiceDetailDtoList(shanghaiPlatformInvoiceDetailProcess);
                    serviceInvoiceIds.addAll(this.doCreateService(invoiceApplyForm));
                }
            }
        });
        return serviceInvoiceIds;
    }

    /**
     * 执行创建
     *
     * @param invoiceApplyForm                      原申请信息（创建时只取平台组织信息、发票等信息）
     */
    private List<String> doCreateService(InvoiceApplyForm invoiceApplyForm) {
        List<String> invoiceIds = new ArrayList<>();

        List<String> dataTradeIds = new ArrayList<>();
        List<ApplyInvoiceDetailDto> detailProcessDataList = invoiceApplyForm.getApplyInvoiceDetailDtoList();
        Assert.notEmpty(detailProcessDataList, "服务费发票明细不存在");

        List<InvoiceApplyForm> splitSuccessInvoiceApply = new ArrayList<>();

        InvoiceApplyForm originalInvoiceApplyProcess = new InvoiceApplyForm();
        BeanUtils.copyProperties(invoiceApplyForm, originalInvoiceApplyProcess);
        //河南固买需要拆票(一般服务费发票都是增值税专用发票)
        if (invoiceApplyForm.getSale_organization_id().getId().equals(PlatformConst.PLATFORM_ORGANIZATION_IDS.get(1))
                && InvoiceType.VatSpecial == InvoiceType.valueOf(invoiceApplyForm.getType())) {
            this.recursionProcessInvoiceDetailProcess(invoiceApplyForm, splitSuccessInvoiceApply, NANYANG_VAT_SPECIAL_MAX_INVOICE, originalInvoiceApplyProcess);
        } else {
            this.recursionProcessInvoiceDetailProcess(invoiceApplyForm, splitSuccessInvoiceApply, MAX_INVOICE, originalInvoiceApplyProcess);
        }
        //构建发票明细
        for (InvoiceApplyForm splitInvoiceApplyForm : splitSuccessInvoiceApply) {
            BigDecimal total = BigDecimal.ZERO, priceAmount = BigDecimal.ZERO, taxAmount = BigDecimal.ZERO;
            List<ApplyInvoiceDetailDto> canInvoiceDetailProcessList = splitInvoiceApplyForm.getApplyInvoiceDetailDtoList();
            List<InvoiceDetail> persistData = new ArrayList<>();
            for (ApplyInvoiceDetailDto canDetail : canInvoiceDetailProcessList) {
                total = BigDecimalUtils.add(total, canDetail.getTotal());
                priceAmount = BigDecimalUtils.add(priceAmount, canDetail.getAmount());

                taxAmount = BigDecimalUtils.add(taxAmount, canDetail.getTax());

                InvoiceDetailExtendsAttr extendsAttr = InvoiceDetailExtendsAttr.builder()
                        .orderGoodsId(canDetail.getOrder_goods_id())
                        .vendorOrderId(canDetail.getVendor_order_id())
                        .customerOrderId(canDetail.getCustomer_order_id())
                        .ifApprove(false)
                        .build();

                persistData.add(InvoiceDetail.builder()
                        .trade_id(canDetail.getId())
                        .name(canDetail.getName())
                        .model_spec(canDetail.getModel_spec())
                        .unit(canDetail.getUnit())
                        .golden_tax(PlatformConst.SERVICE_INVOICE_GOLDEN_TAX)
                        .quantity(canDetail.getQuantity())
                        .price(canDetail.getPrice())
                        .gross_price(canDetail.getPretax_price())
                        .amount(canDetail.getAmount())
                        .tax_rate(canDetail.getTax_rate())
                        .tax(canDetail.getTax())
                        .total(canDetail.getTotal())
                        .offset_quantity(BigDecimal.ZERO)
                        .offset_total(BigDecimal.ZERO)
                        .extends_attr(InvoiceDetailExtendsAttr.conversion(extendsAttr).orElseThrow(IllegalArgumentException::new))
                        .build());
                dataTradeIds.add(canDetail.getId());
            }
            Assert.isTrue(!CollectionUtils.isEmpty(dataTradeIds), "采购交易id数据异常");


            //替换英文括号为中文括号
            InvoiceApply applyEntity = new InvoiceApply();
            BeanUtils.copyProperties(splitInvoiceApplyForm, applyEntity);
            String purchaseName = applyEntity.getPurchase_name().replace("(", "（").replace(")", "）").trim();

            List<Trade> tradeEntityList = JdbcTemplateUtils.builder(ModelConst.T_TRADE).selectField("*")
                    .in("id", dataTradeIds)
                    .queryForPOJOList(Trade.class);

            //通过采购交易获取客户对应的平台
//            List<Trade> saleTradeList = JdbcTemplateUtils.builder(ModelConst.T_TRADE).selectField(" platform_organization_id ")
//                    .eq("direction", TradeDirection.Sale.name())
//                    .eq("order_goods_id", tradeEntityList.stream().map(Trade::getOrder_goods_id).collect(Collectors.toList()))
//                    .queryForPOJOList(Trade.class);
//            Assert.isTrue(saleTradeList != null && saleTradeList.stream().map(Trade::getPlatform_organization_id).collect(Collectors.toList()).size() == 1, "对应平台组织不唯一");


            applyEntity
                    .setType(splitInvoiceApplyForm.getType())
                    .setTotal(total)
                    //生成服务费的时候，已经收取了服务费，所以可核销金额和可申请结算金额都是0
                    .setWrite_off_amount(BigDecimal.ZERO)
                    .setWritten_off_amount(BigDecimal.ZERO)
                    .setOffset_total(BigDecimal.ZERO)
                    .setPrice_amount(priceAmount)
                    .setTax_amount(taxAmount)
                    .setTax_rate(PlatformConst.PLATTAXRATE.toString())
                    .setStatuss(InvoiceStatus.New.name())
                    .setExtends_attr(InvoiceExtendsAttr.conversion(InvoiceExtendsAttr.builder().ifApprove(false).taxRate(PlatformConst.PLATTAXRATE.toString()).build()).orElseThrow(IllegalArgumentException::new))
                    .setPurchase_name(purchaseName)
                    .setSale_organization_id(tradeEntityList.get(0).getPlatform_organization_id())
                    .setSale_organization_name(tradeEntityList.get(0).getPlatform_organization_name())
                    .setTrade_order_type(splitInvoiceApplyForm.getTrade_order_type());
            if(invoiceApplyForm.getDirection().equals("Sale")){
                applyEntity.setPlatform_organization_id(applyEntity.getSale_organization_id().getId());
            }else{
                applyEntity.setPlatform_organization_id(applyEntity.getPurchase_organization_id().getId());
            }
            Map<String, Object> createMap = BeanUtil.beanToMap(applyEntity);
            BoServiceUtils.createBo(ModelConst.T_INVOICE_APPLY, Collections.singletonList(createMap));
            String invoiceId = createMap.get("id").toString();
            invoiceIds.add(invoiceId);

            //持久化交易维度开票明细
            persistData.forEach(item -> item.setInvoice_apply_id(invoiceId));
            List<Map<String, Object>> mapList = persistData.stream().map(BeanUtil::beanToMap).collect(Collectors.toList());
            BoServiceUtils.createBo(ModelConst.T_INVOICE_DETAIL, mapList);
            persistData.forEach(item -> {
                String invoiceDetailId = mapList.stream().filter(t -> t.get("trade_id").toString().equals(item.getTrade_id())).map(t -> t.get("id").toString()).findAny().get();
                item.setId(invoiceDetailId);
            });
            //持久化账单维度开票明细
            invoiceDetailBillService.createService(persistData);
        }
        return invoiceIds;
    }

    /**
     * 构建交易维度的开票明细
     * @param billIds
     * @return
     */
    private List<ApplyInvoiceDetailDto> getCanApplyServiceInvoice(List<String> billIds){
        List<ApplyInvoiceDetailDto> results = new ArrayList<>();
        List<Bill> bills = billService.list(BillQuery.builder().ids(billIds).actual(true).build());
        billService.applyRuleValid(bills,true);

        String sql = "select detailBill.* from "+JdbcTemplateUtils.getTableName(ModelConst.T_INVOICE_DETAIL_BILL)+" as detailBill " +
                " left join "+JdbcTemplateUtils.getTableName(ModelConst.T_INVOICE_APPLY) + " as invoice on invoice.id = detailBill.invoice_apply_id" +
                " where detailBill.deleted = 0 and invoice.business_type = 'Service' and invoice.offset_total <> invoice.total " +
                " and invoice.statuss in (:statuss)";
        Map<String, Object> param = MapBuilder.create(new HashMap<String, Object>()).put("statuss", InvoiceStatus.applied()).build();
        List<InvoiceDetailBill> alreadyInvoiceBillList = JdbcTemplateUtils.queryForPOJOList(sql, param, ModelConst.T_INVOICE_DETAIL_BILL, InvoiceDetailBill.class);

        for (Bill bill : bills) {
            BigDecimal billTotal = bill.getTotal();

            if (!CollectionUtils.isEmpty(alreadyInvoiceBillList)) {
                List<InvoiceDetailBill> alreadyInvoiceBill = alreadyInvoiceBillList.stream().filter(t -> t.getBill_id().equals(bill.getId())).collect(Collectors.toList());
                BigDecimal alreadyTotal = CalculatorUtils.sum(alreadyInvoiceBill, BiFunctionConst.invoiceBillTotalFun);
                billTotal = BigDecimalUtils.sub(bill.getTotal(), alreadyTotal);

                //如果账单金额大于0，则可申请金额必须大于0；如果小于0，则可申请金额必须小于0
                boolean verifyCanApply = (Double.compare(bill.getTotal().doubleValue(), 0.0) == 1 && Double.compare(billTotal.doubleValue(), 0.0) == 1)
                        || (Double.compare(bill.getTotal().doubleValue(), 0.0) == -1 && Double.compare(billTotal.doubleValue(), 0.0) == -1);
                Assert.isTrue(verifyCanApply, "账单编号【" + bill.getId() + "】已全部申请");
            }

            bill.setTotal(billTotal);
        }
        bills.stream().collect(Collectors.groupingBy(Bill::getTrade_id)).forEach((key, value) -> {
            BigDecimal total = BigDecimal.ZERO;
            for (Bill bill : value) {
                total = BigDecimalUtils.add(total, bill.getTotal());
            }

            Bill bill = value.get(0);
            BigDecimal amount = BigDecimalUtils.round(TaxRateUtils.calculateTaxBefore(total, bill.getTax_rate()), 2);
            log.info("服务费发票含税{}，未税{}，税率：{}", total,amount,bill.getTax_rate());
            BigDecimal tax = BigDecimalUtils.sub(total, amount);

            Summary summary = Summary.conversion(bill.getSummary());
            String modelSpec = summary.getModel() + " " + summary.getSpec();

            ApplyInvoiceDetailDto applyInvoiceDetailDto = ApplyInvoiceDetailDto.builder()
                    .id(key)
                    .order_goods_id(bill.getOrder_goods_id())
                    .vendor_order_id(bill.getVendor_order_id())
                    .customer_order_id(bill.getCustomer_order_id())
                    .name("服务费")
                    .model_spec(modelSpec)
                    .unit(summary.getUnit())
                    .quantity(BigDecimal.ONE)
                    .price(total)
                    .pretax_price(amount)
                    .tax_rate(bill.getTax_rate())
                    .total(total)
                    .amount(amount)
                    .tax(tax)
                    .build();
            results.add(applyInvoiceDetailDto);
        });

        return results;

    }

    /**
     * 执行回写发票可核销金额和已核销金额
     *
     * @param invoiceApplyIds  参与回写发票单Id列表
     */
    @Override
    public void writeBackInvoiceWriteOffAmountByInvoiceApply(List<String> invoiceApplyIds){
        Assert.notEmpty(invoiceApplyIds, "回写的发票ID不能为空");
        List<InvoiceApply> invoiceApplyList = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField("*")
                .in("id", invoiceApplyIds)
                .queryForPOJOList(InvoiceApply.class);
        if (!CollectionUtils.isEmpty(invoiceApplyList)) {
            //发票所有的有效结算明细
            StringBuffer sql = new StringBuffer(" select detail.* from ")
                    .append(JdbcTemplateUtils.getTableName(ModelConst.T_SETTLE_DETAIL)).append(" as detail")
                    .append(" left join ").append(JdbcTemplateUtils.getTableName(ModelConst.T_SETTLE)).append(" as settle")
                    .append(" on detail.settle_id = settle.id")
                    .append(" where settle.deleted = 0 and detail.deleted = 0")
                    .append(" and detail.invoice_apply_id in (:invoiceApplyId)")
                    .append(" and settle.statuss in (:statuss)");
            MapBuilder<String, Object> mapBuilder = MapBuilder.create(new HashMap<>());
            mapBuilder.put("invoiceApplyId", invoiceApplyIds)
                    .put("statuss", SettleStatus.normalStatus());
            List<SettleDetail> settleDetailEntityAllList = JdbcTemplateUtils.queryForPOJOList(sql.toString(), mapBuilder.build(), ModelConst.T_SETTLE_DETAIL, SettleDetail.class);

            for (String invoiceId : invoiceApplyIds){
                List<SettleDetail> settleDetailList = settleDetailEntityAllList.stream().filter(t -> t.getInvoice_apply_id().equals(invoiceId)).collect(Collectors.toList());
                //计算发票的已核销金额
                BigDecimal settleDetailTotal = BigDecimal.ZERO;
                if(!CollectionUtils.isEmpty(settleDetailList)){
                    settleDetailTotal = CalculatorUtils.sum(settleDetailList, BiFunctionConst.settleDetailTotalFun);
                }
                //蓝票
                InvoiceApply invoiceApplyEntity = invoiceApplyList.stream().filter(t -> t.getId().equals(invoiceId)).findAny().get();

                List<InvoiceDetailBill> invoiceDetailBillEntityList = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_DETAIL_BILL).selectField("*")
                        .eq("invoice_apply_id", invoiceId).queryForPOJOList(InvoiceDetailBill.class);
                //已红冲的金额
                BigDecimal offsetInvoiceTotal = BigDecimal.ZERO;
                for (InvoiceDetailBill invoiceDetailBillEntity : invoiceDetailBillEntityList) {
                    //蓝票对应红票
                    List<InvoiceDetailBill> invoiceDetailBillRedEntityList = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_DETAIL_BILL)
                            .selectField("*")
                            .eq("offset_id", invoiceDetailBillEntity.getId()).queryForPOJOList(InvoiceDetailBill.class);
                    if (!CollectionUtils.isEmpty(invoiceDetailBillRedEntityList)) {
                        BigDecimal invoiceDetailBillRedSum = CalculatorUtils.sum(invoiceDetailBillRedEntityList, BiFunctionConst.invoiceBillTotalFun);
                        offsetInvoiceTotal = BigDecimalUtils.add(offsetInvoiceTotal, invoiceDetailBillRedSum);
                    }
                }
                //计算公式为：发票可核销金额 = 发票总金额-红票-已核销金额
                BigDecimal writeOffAmount = BigDecimalUtils.sub(BigDecimalUtils.sub(invoiceApplyEntity.getTotal(), offsetInvoiceTotal), settleDetailTotal);

                JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY)
                        .putFieldAndVal("write_off_amount", writeOffAmount)
                        .putFieldAndVal("written_off_amount", settleDetailTotal)
                        .eq("id", invoiceApplyEntity.getId())
                        .update();
            }

        }
    }


    /**
     * 根据条件关闭申请单
     * @param invoiceIds 要关闭的申请单ID
     * @param status 符合关闭条件的状态
     */
    @Override
    public List<String> close(List<String> invoiceIds, List<InvoiceStatus> status){
        log.info("================关闭发票申请单ID：{}", String.join(",", invoiceIds));
        Assert.notEmpty(invoiceIds,"发票申请单编号不能为空");
        List<String> result = new ArrayList<>();
        List<String> statusStrList = status.stream().map(InvoiceStatus::name).collect(Collectors.toList());
        List<InvoiceApply> invoiceApplyList = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY)
                .selectField("*")
                .eq("deleted",0).in("id", invoiceIds)
                .in("statuss", statusStrList)
                .queryForPOJOList(InvoiceApply.class);
        Assert.notEmpty(invoiceApplyList,"未获取到对应的发票申请单");
        //
        List<InvoiceApply> chooseInvoiceApplyList = invoiceApplyList.stream().filter(t -> InvoiceStatus.allowToClose().contains(Enum.valueOf(InvoiceStatus.class, t.getStatuss()))).collect(Collectors.toList());

        List<String> goldenTaxIds = new ArrayList<>();//金税记录ID

        Map<String, Object> param = new HashMap<>();
        for(InvoiceApply invoice : chooseInvoiceApplyList){
            param.clear();
            InvoiceExtendsAttr extendsAttr = InvoiceExtendsAttr.conversion(invoice.getExtends_attr()).orElse(InvoiceExtendsAttr.builder().build());
            boolean ifOffset = StringUtils.isNotBlank(extendsAttr.getOffsetIds());
            boolean ifCancel = InvoiceStatus.Cancel.name().equals(invoice.getStatuss());
            boolean ifRed = InvoiceBusinessType.RedProduct.name().equals(invoice.getBusiness_type()) || InvoiceBusinessType.RedService.name().equals(invoice.getBusiness_type());
            Assert.isTrue(!ifOffset, "已核销的发票不能关闭");
            Assert.isTrue(!ifCancel, "已取消的发票不能关闭");
            Assert.isTrue(!ifRed, "红字发票不能关闭");
            Assert.isTrue(invoice.getOffset_total().compareTo(BigDecimal.ZERO)==0, "不可对已经产生红字发票的蓝票进行关闭");

            //如果关闭的发票单状态不为已开票和待收票，防止可能是因为回退发票所以清除开票信息（状态不为已开票和待收票,需要清除开票信息）
            if (!InvoiceStatus.Done.name().equals(invoice.getStatuss()) && !InvoiceStatus.ReceiverConfirming.name().equals(invoice.getStatuss()) ) {
                param.put("code","");
                param.put("number","");
                param.put("date", null);
                param.put("confirm_time", null);
            }
            param.put("statuss", InvoiceStatus.Cancel.name());
            JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY)
                    .putFieldAndVal(param)
                    .eq("id", invoice.getId())
                    .update();
            result.add(invoice.getId());
            if (invoice.getGolden_tax_id() != null) {
                goldenTaxIds.add(invoice.getGolden_tax_id());
            }
        }

        //删除金税发票申请单
        goldenTaxService.delete(goldenTaxIds);

        //回写交易记录的可申请发票数量和发票状态
        SingleResponse<BOList> resultList = BoServiceUtils.findListByIn(ModelConst.T_INVOICE_DETAIL, "id", invoiceIds);
        List<String> tradeIds = resultList.getData().getData().stream().map(t -> t.get("trade_id").toString()).collect(Collectors.toList());
        tradeService.writeBackAllowedInvoiceQuantity(tradeIds);
        //回写发票申请单对应交易记录的发票号码
        writeBackTradeInvoiceNumber(invoiceIds);

        return result;
    }

    /**
     * 回写发票申请单对应交易记录的发票号码
     * @param invoiceIds 发票申请单ID
     */
    @Override
    public void writeBackTradeInvoiceNumber(Collection<String> invoiceIds){
        if(!CollectionUtils.isEmpty(invoiceIds)){
            for (String invoiceApplyId : invoiceIds){
                InvoiceApply invoiceApply = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField("*")
                        .eq("id", invoiceApplyId)
                        .queryForPOJO(InvoiceApply.class);

                if(InvoiceBusinessType.Product.name().equals(invoiceApply.getBusiness_type())){
                    List<InvoiceDetail> invoiceDetailList = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_DETAIL).selectField("*")
                            .eq("invoice_apply_id", invoiceApply.getId())
                            .queryForPOJOList(InvoiceDetail.class);
                    Set<String> tradeIds = invoiceDetailList.stream().map(InvoiceDetail::getTrade_id).collect(Collectors.toSet());
                    List<Trade> tradeList = JdbcTemplateUtils.builder(ModelConst.T_TRADE).selectField("*")
                            .in("id", tradeIds)
                            .queryForPOJOList(Trade.class);
                    for (Trade trade : tradeList){
                        List<String> result = new ArrayList<>();
                        //查找该交易所有的发票单明细账单
                        List<InvoiceDetailBill> detailBillList = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_DETAIL_BILL).selectField("*")
                                .in("trade_id", trade.getId())
                                .queryForPOJOList(InvoiceDetailBill.class);
                        //只需要蓝票的发票明细
                        Set<String> invoiceApplyIdSet = detailBillList.stream().filter(t -> StringUtils.isEmpty(t.getOffset_id()))
                                .map(InvoiceDetailBill::getInvoice_apply_id).collect(Collectors.toSet());
                        List<InvoiceApply> invoiceApplyList = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField("*")
                                .in("id", invoiceApplyIdSet)
                                .queryForPOJOList(InvoiceApply.class);
                        for (InvoiceApply apply : invoiceApplyList){
                            BigDecimal offsetTotal = apply.getOffset_total() == null ? BigDecimal.ZERO : apply.getOffset_total();
                            if(offsetTotal.compareTo(apply.getTotal()) < 0 &&
                                    (InvoiceStatus.ReceiverConfirming.name().equals(apply.getStatuss()) || InvoiceStatus.Done.name().equals(apply.getStatuss()) )){
                                result.add(apply.getNumber());
                            }

                        }
                        List<String> invoiceNumberList = result.stream().distinct().collect(Collectors.toList());
                        Map<String, Object> param = MapBuilder.create(new HashMap<String, Object>())
                                .put("id",trade.getId())
                                .put("invoice_number", StringUtils.join(invoiceNumberList, ","))
                                .build();
                        BoServiceUtils.updateMainBo( ModelConst.T_TRADE, param);
                    }

                }
            }
        }
    }

    @Override
    public void updateInvoice(InvoiceApplyUpdateForm invoiceDto) {
        InvoiceApply invoiceApply = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField("*")
                .eq("id", invoiceDto.getId()).queryForPOJO(InvoiceApply.class);
        Assert.notNull(invoiceApply, "未查到要修改的发票单");

        //判断是否为平台用户 非平台用户只有退回和新建状态下可修改 平台用户除了取消状态都可修改
        if (!RoleUtils.existByUserIdAndRoleName(RequestContext.getCorpLoginId(),PlatformConst.ROLE_PM_ADMIN)) {
            Assert.isTrue(InvoiceStatus.New.name().equals(invoiceApply.getStatuss()) || InvoiceStatus.Returned.name().equals(invoiceApply.getStatuss()),
                    "该状态发票不可修改");
        } else {
            Assert.isTrue(InvoiceStatus.applied().contains(invoiceApply.getStatuss()), "该状态发票不可修改");
        }
        Map<String, Object> param = MapBuilder.create(new HashMap<String, Object>())
                .put("id", invoiceApply.getId())
                .put("type", invoiceDto.getType())
                .put("number", invoiceDto.getNumber())
                .put("code", invoiceDto.getCode())
                .put("drawer", invoiceDto.getDrawer())
                .put("date", invoiceDto.getDate())
                .put("purchase_name", invoiceDto.getPurchase_name())
                .put("purchase_number", invoiceDto.getPurchase_number())
                .put("purchase_address", invoiceDto.getPurchase_address())
                .put("purchase_phone", invoiceDto.getPurchase_phone())
                .put("purchase_bank", invoiceDto.getPurchase_bank())
                .put("purchase_account", invoiceDto.getPurchase_account())
                .put("receiver", invoiceDto.getReceiver())
                .put("address", invoiceDto.getAddress())
                .put("phone", invoiceDto.getPhone())
                .put("memo", invoiceDto.getMemo()).build();

        if(null != invoiceDto.getPrice_amount() && null != invoiceDto.getTax_amount()){
            BigDecimal total = invoiceDto.getPrice_amount().add(invoiceDto.getTax_amount());
            Assert.isTrue(total.compareTo(invoiceApply.getTotal()) ==0, "未税+税额不等于含税");
            param.put("price_amount", invoiceDto.getPrice_amount());
            param.put("tax_amount", invoiceDto.getTax_amount());
        }
        BoServiceUtils.updateMainBo(ModelConst.T_INVOICE_APPLY, param);
        //修改发票明细
        updateInvoiceDetail(invoiceDto.getInvoiceDetails());

        if(StringUtils.isNotEmpty(invoiceApply.getGolden_tax_id())){
            GoldenTax goldenTax = JdbcTemplateUtils.builder(ModelConst.T_GOLDEN_TAX).selectField("*").eq("id", invoiceApply.getGolden_tax_id())
                    .queryForPOJO(GoldenTax.class);
            if (goldenTax != null) {
                param.clear();
                param.put("id", goldenTax.getId());
                param.put("purchase_name", invoiceDto.getPurchase_name());
                param.put("purchase_number", invoiceDto.getPurchase_number());
                param.put("purchase_address_phone", invoiceDto.getPurchase_address() + " " + invoiceDto.getPurchase_phone());
                param.put("purchase_bank_account", invoiceDto.getPurchase_bank() + " " + invoiceDto.getPurchase_account());
                param.put("memo", invoiceDto.getMemo());
                BoServiceUtils.updateMainBo( ModelConst.T_GOLDEN_TAX, param);
            }

        }
    }

    /**
     * 修改开票明细
     */
    public void updateInvoiceDetail(List<InvoiceDetail> invoiceDetailsDto){
        if (CollectionUtils.isEmpty(invoiceDetailsDto)) {
            return;
        }

        StopWatch stopWatch = new StopWatch();
        stopWatch.start("修改发票明细");

        List<InvoiceDetail> invoiceDetails = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_DETAIL).selectField("*")
                .in("id", invoiceDetailsDto.stream().map(InvoiceDetail::getId).collect(Collectors.toList()))
                .queryForPOJOList(InvoiceDetail.class);

        if (CollectionUtils.isEmpty(invoiceDetails)) {
            return;
        }

        boolean ifReCalculateInvoice = false;//如果修改金额就重新计算发票金额
        for (InvoiceDetail invoiceDetail : invoiceDetails) {

            InvoiceDetail process = invoiceDetailsDto.stream().filter(t -> t.getId().equals(invoiceDetail.getId())).findAny().get();
            invoiceDetail.setModel_spec(process.getModel_spec()).setName(process.getName()).setUnit(process.getUnit());

            //金额不一致，判断是否允许修改金额
            if (null != process.getTotal() && invoiceDetail.getTotal().compareTo(process.getTotal()) != 0) {
                InvoiceApply invoiceApply = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField("*")
                        .eq("id", invoiceDetail.getInvoice_apply_id())
                        .queryForPOJO(InvoiceApply.class);

                //开票后、已被红冲、已取消、红字、服务费发票
                boolean ifMakeAfter = InvoiceStatus.Done.name().equals(invoiceApply.getStatuss()) || InvoiceStatus.ReceiverConfirming.name().equals(invoiceApply.getStatuss());
                boolean ifOffset = invoiceDetail.getOffset_quantity().compareTo(BigDecimal.ZERO) != 0;
                boolean ifCancel =  InvoiceStatus.Cancel.name().equals(invoiceApply.getStatuss());
                boolean ifRed = InvoiceBusinessType.RedProduct.name().equals(invoiceApply.getBusiness_type());
                boolean ifService = InvoiceBusinessType.Service.name().equals(invoiceApply.getBusiness_type()) || InvoiceBusinessType.RedService.name().equals(invoiceApply.getBusiness_type());

                Assert.isTrue(!ifMakeAfter, "开票后不能修改金额");
                Assert.isTrue(!ifOffset, "已核销的发票不能修改金额");
                Assert.isTrue(!ifCancel, "已取消的发票不能修改金额");
                Assert.isTrue(!ifRed, "红字发票不能修改金额");
                Assert.isTrue(!ifService, "服务费发票不能修改金额");

                invoiceDetail.setAmount(TaxRateUtils.calculateTaxBefore(process.getTotal(),invoiceDetail.getTax_rate()))
                        .setTax(process.getTotal().subtract(invoiceDetail.getAmount()))
                        .setPrice(BigDecimalUtils.div(invoiceDetail.getAmount(), invoiceDetail.getQuantity()));
                invoiceDetail.setGross_price(BigDecimalUtils.div(invoiceDetail.getTotal(), invoiceDetail.getQuantity()));
                ifReCalculateInvoice = true;
            }
            BoServiceUtils.updateMainBo(ModelConst.T_INVOICE_DETAIL, BeanUtil.beanToMap(invoiceDetail));
        }

        //重新计算发票单金额
        if (ifReCalculateInvoice) {

            //重新计算发票的总额，税额，税前总额
            List<String> invoidIds  = invoiceDetails.stream().map(InvoiceDetail::getInvoice_apply_id).distinct().collect(Collectors.toList());
            String sql = "select invoice_apply_id, ifnull(sum(total), 0) total, ifnull(sum(tax), 0) tax,ifnull(sum(amount), 0) amount from "
                    + JdbcTemplateUtils.getTableName(ModelConst.T_INVOICE_DETAIL)+" where invoice_apply_id in (:invoiceIds) "
                    +" group by invoice_apply_id";
            Map<String, Object> param = MapBuilder.create(new HashMap<String, Object>()).put("invoiceIds", invoidIds).build();
            List<InvoiceDetail> invoiceDetailSumList = JdbcTemplateUtils.queryForPOJOList(sql, param, ModelConst.T_INVOICE_DETAIL, InvoiceDetail.class);
            Assert.isTrue(invoidIds.size() == invoiceDetailSumList.size(),"需要重新计算发票金额和实际要重新计算发票金额条数不符");
            for (String invoiceApplyId : invoidIds){
                InvoiceDetail invoiceDetail = invoiceDetailSumList.stream().filter(t->t.getInvoice_apply_id().equals(invoiceApplyId)).findFirst().get();
                InvoiceApply apply = new InvoiceApply();
                apply.setId(invoiceApplyId);
                apply.setTotal(invoiceDetail.getTotal())
                        .setTax_amount(invoiceDetail.getTax())
                        .setPrice_amount(invoiceDetail.getAmount());
                BoServiceUtils.updateMainBo(ModelConst.T_INVOICE_APPLY, BeanUtil.beanToMap(apply));
            }

            invoiceDetailBillService.update(invoiceDetails);
        }

        stopWatch.stop();
        String prettyPrint = stopWatch.prettyPrint();
        log.info(prettyPrint);
    }

    /**
     * 货物发票单红冲.
     *
     * @param offsetInvoice 红冲参数
     * @param invoice       源蓝票单
     * @return 保存成功的红字发票单
     */
    @Override
    public InvoiceApply goodsOffset(@NonNull OffsetInvoice offsetInvoice, @NonNull InvoiceApply invoice) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("货物发票发票单红冲");

        List<String> offsetInvoiceDetailIds = null;
        Collection<OffsetInvoiceDetail> offsetInvoiceDetails = offsetInvoice.getOffsetInvoiceDetails();
        if (!CollectionUtils.isEmpty(offsetInvoiceDetails)) {
            offsetInvoiceDetailIds = offsetInvoiceDetails.stream().map(OffsetInvoiceDetail::getId).collect(Collectors.toList());
        }

        //获取发票单可红冲的发票明细
        InvoiceDetailQuery invoiceDetailQuery = InvoiceDetailQuery.builder()
                .canOffsetQuantity(true)
                .ids(offsetInvoiceDetailIds)
                .invoiceApplyIds(Collections.singletonList(invoice.getId()))
                .businessTypes(Arrays.asList(InvoiceBusinessType.RedProduct.name(), InvoiceBusinessType.Product.name()))
                .build();
        List<InvoiceDetail> detailList = invoiceDetailService.listInvoiceDetail(invoiceDetailQuery);
        Assert.isTrue(!CollectionUtils.isEmpty(detailList), "未获取到可执行的发票信息");

        List<InvoiceDetail> saveDetails = new ArrayList<>();
        BigDecimal offsetTotal = BigDecimal.ZERO, offsetPriceAmount = BigDecimal.ZERO, offsetTaxAmount = BigDecimal.ZERO;

        //获取已红冲的红票信息
        List<String> invoiceDetailIds = detailList.stream().map(InvoiceDetail::getId).collect(Collectors.toList());
        InvoiceDetailQuery offsetInvoiceDetailQuery = InvoiceDetailQuery.builder()
                .canOffsetQuantity(true)
                .offsetIds(invoiceDetailIds)
                .invoiceStatuses(InvoiceStatus.applied())
                .businessTypes(Arrays.asList(InvoiceBusinessType.RedProduct.name()))
                .build();
        List<InvoiceDetail> alreadyOffsetList = invoiceDetailService.listInvoiceDetail(offsetInvoiceDetailQuery);

        Set<BigDecimal> taxRateList = new HashSet<>();
        for (InvoiceDetail detail : detailList) {
            //获取明细已红冲金额和数量
            BigDecimal alreadyOffsetTotal = BigDecimal.ZERO, alreadyOffsetQuantity = BigDecimal.ZERO;
            List<InvoiceDetail> mappingOffsetList = alreadyOffsetList.stream().filter(t -> t.getOffset_id().equals(detail.getId())).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(mappingOffsetList)) {
                for (InvoiceDetail mappingOffset : mappingOffsetList) {
                    alreadyOffsetTotal = BigDecimalUtils.add(alreadyOffsetTotal, mappingOffset.getTotal());
                    alreadyOffsetQuantity = BigDecimalUtils.add(alreadyOffsetQuantity, mappingOffset.getQuantity());
                }
            }

            BigDecimal canOffsetQuantity = BigDecimalUtils.sub(detail.getQuantity(), alreadyOffsetQuantity);
            if (canOffsetQuantity.compareTo(BigDecimal.ZERO) == 0) {
                //已红冲完
                continue;
            }

            BigDecimal offsetQuantity = canOffsetQuantity;//可红冲数量
            //判断客户是否手动部分红冲
            if (!CollectionUtils.isEmpty(offsetInvoiceDetails)) {

                List<OffsetInvoiceDetail> applyOffsetDetails = offsetInvoiceDetails.stream().filter(t -> t.getId().equals(detail.getId())).collect(Collectors.toList());
                Assert.isTrue(applyOffsetDetails.size()>0, "发票红冲明细异常");
                if (null != applyOffsetDetails.get(0).getQuantity()) {
                    Assert.isTrue(applyOffsetDetails.get(0).getQuantity().compareTo(canOffsetQuantity) != 1, "红冲数量不可大于可红冲数量");
                    offsetQuantity = applyOffsetDetails.get(0).getQuantity();
                }
            }

            BigDecimal offsetTotalTemp = BigDecimalUtils.round(BigDecimalUtils.mul(offsetQuantity, detail.getGross_price()), 2);
            //最后一次红冲
            if (offsetQuantity.compareTo(canOffsetQuantity) == 0) {
                offsetTotalTemp = BigDecimalUtils.sub(detail.getTotal(), alreadyOffsetTotal);
            }

            BigDecimal amount = BigDecimalUtils.round(TaxRateUtils.calculateTaxBefore(offsetTotalTemp, detail.getTax_rate()), 2);
            BigDecimal tax = BigDecimalUtils.sub(offsetTotalTemp, amount);

            //构建货物红字明细 未设置ID和invoiceApplyId
            InvoiceDetail offsetInvoiceDetail = InvoiceDetail.builder()
                    .offset_id(detail.getId())
                    .trade_id(detail.getTrade_id())
                    .name(detail.getName())
                    .model_spec(detail.getModel_spec())
                    .unit(detail.getUnit())
                    .quantity(offsetQuantity)
                    .tax_rate(detail.getTax_rate())
                    .total(offsetTotalTemp)
                    .amount(amount)
                    .tax(tax)
                    .offset_quantity(BigDecimal.ZERO)
                    .offset_total(BigDecimal.ZERO)
                    .price(detail.getPrice())
                    .gross_price(detail.getGross_price())
                    .extends_attr(InvoiceDetailExtendsAttr.offset(detail.getExtends_attr()))
                    .build();


            offsetTotal = BigDecimalUtils.add(offsetTotal, offsetInvoiceDetail.getTotal());
            offsetPriceAmount = BigDecimalUtils.add(offsetPriceAmount, offsetInvoiceDetail.getAmount());
            offsetTaxAmount = BigDecimalUtils.add(offsetTaxAmount, offsetInvoiceDetail.getTax());
            saveDetails.add(offsetInvoiceDetail);
            taxRateList.add(offsetInvoiceDetail.getTax_rate());

            detail.setOffset_quantity(BigDecimalUtils.add(alreadyOffsetQuantity, offsetInvoiceDetail.getQuantity()));
            detail.setOffset_total(BigDecimalUtils.add(alreadyOffsetTotal, offsetInvoiceDetail.getTotal()));
            //更新蓝票红冲数据
            BoServiceUtils.updateMainBo(ModelConst.T_INVOICE_DETAIL, BeanUtil.beanToMap(detail));
        }


        //验证未税+税额=小计金额计算
        Assert.isTrue(offsetTotal.compareTo(offsetPriceAmount.add(offsetTaxAmount)) == 0, "未税金额+税额不等于含税金额");
        //持久化红票发票
        InvoiceApply invoiceApply = InvoiceApply.builder()
                .total(offsetTotal)
                .apply_member_id(RequestContext.getCorpLoginId())
                .apply_member_name(RequestContext.getLoginRealName())
                .statuss(InvoiceStatus.DrawerConfirming.name())
                .price_amount(offsetPriceAmount)
                .tax_amount(offsetTaxAmount)
                .tax_rate(invoice.getTax_rate())
                .offset_id(invoice.getId())
                .offset_total(BigDecimal.ZERO)
                .type(invoice.getType())
                .business_type(InvoiceBusinessType.getRedType(invoice.getBusiness_type()).name())
                .purchase_name(invoice.getPurchase_name())
                .purchase_number(invoice.getPurchase_number())
                .purchase_address(invoice.getPurchase_address())
                .purchase_phone(invoice.getPurchase_phone())
                .purchase_bank(invoice.getPurchase_bank())
                .purchase_account(invoice.getPurchase_account())
                .receiver(invoice.getReceiver())
                .phone(invoice.getPhone())
                .address(invoice.getAddress())
                .purchase_organization_id(invoice.getPurchase_organization_id())
                .purchase_organization_name(invoice.getPurchase_organization_name())
                .sale_organization_id(invoice.getSale_organization_id())
                .sale_organization_name(invoice.getSale_organization_name())
                .written_off_amount(BigDecimal.ZERO)
                .extends_attr(InvoiceExtendsAttr.conversion(InvoiceExtendsAttr.builder().taxRate(PlatformConst.PLATTAXRATE.toString()).build()).get())
                .apply_time(new Date())
                .build();
        SingleResponse<BO> invoiceResponse = BoServiceUtils.createBo(ModelConst.T_INVOICE_APPLY, Collections.singletonList(BeanUtil.beanToMap(invoiceApply)));
        invoiceApply.setId(invoiceResponse.getData().getBizObjectId());
        //持久化红票明细
        for(InvoiceDetail invoiceDetail : saveDetails){
            invoiceDetail.setInvoice_apply_id(invoiceApply.getId());
        }
        List<Map<String, Object>> collect = saveDetails.stream().map(BeanUtil::beanToMap).collect(Collectors.toList());
        BoServiceUtils.createBo(ModelConst.T_INVOICE_DETAIL, collect);
        //将创建时生成的ID赋值到对象上
        saveDetails = collect.stream().map(t -> BeanUtil.mapToBean(t,InvoiceDetail.class, true)).collect(Collectors.toList());
        //红冲发票账单明细记录
        invoiceDetailBillService.offsetGoods(saveDetails);
        //回写交易记录
        tradeService.writeBackAllowedInvoiceQuantity(detailList.stream().map(InvoiceDetail::getTrade_id).collect(Collectors.toList()));

        stopWatch.stop();
        String prettyPrint = stopWatch.prettyPrint();
        log.info(prettyPrint);
        return invoiceApply;
    }

    @Override
    public PageList<InvoiceApply> page(InvoiceQuery query) {
        PageList<InvoiceApply> pageList = new PageList<>();
        MapBuilder<String, Object> param = MapBuilder.create(new HashMap<>());
        StringBuffer dataSql = new StringBuffer("select * from ").append(JdbcTemplateUtils.getTableName(ModelConst.T_INVOICE_APPLY)).append(" where deleted = 0 ");
        if(RegexUtils.notNull(query.getId())){
            dataSql.append(" and id = :id");
            param.put("id", query.getId());
        }
        if(RegexUtils.notNull(query.getPurchase_name())){
            dataSql.append(" and purchase_name like :purchase_name");
            param.put("purchase_name", "%".concat(query.getPurchase_name()).concat("%"));
        }
        //1=可申请，2=已申请
        if(query.getIs_settle()!=null){
            if(query.getIs_settle()==1){
                dataSql.append(" and write_off_amount > 0 ");
            }else if(query.getIs_settle()==2){
                dataSql.append(" and write_off_amount = 0 ");
            }
        }

        if(RegexUtils.notNull(query.getNumber())){
            dataSql.append(" and number like :number");
            param.put("number", query.getNumber());
        }
        if(RegexUtils.notNull(query.getOffset_id())){
            dataSql.append(" and offset_id = :offset_id");
            param.put("offset_id", query.getOffset_id());
        }
        if(RegexUtils.notNull(query.getTradeOrderType())){
            dataSql.append(" and trade_order_type = :tradeOrderType");
            param.put("tradeOrderType", query.getTradeOrderType());
        }
        if(!CollectionUtils.isEmpty(query.getPurchase_organization_id())){
            dataSql.append(" and JSON_UNQUOTE(purchase_organization_id ->'$.id') in (:purchase_organization_id)");
            param.put("purchase_organization_id", query.getPurchase_organization_id());
        }
        if(!CollectionUtils.isEmpty(query.getSale_organization_id())){
            dataSql.append(" and JSON_UNQUOTE(sale_organization_id ->'$.id') in (:sale_organization_id)");
            param.put("sale_organization_id", query.getSale_organization_id());
        }
        if(!CollectionUtils.isEmpty(query.getBusiness_type())){
            dataSql.append(" and business_type = :business_type");
            param.put("business_type", query.getBusiness_type());
        }
        if(!CollectionUtils.isEmpty(query.getStatuss())){
            dataSql.append(" and statuss in (:statuss)");
            param.put("statuss", query.getStatuss());
        }
        if(!CollectionUtils.isEmpty(query.getDate())){
            dataSql.append(" and date between  :start and :end");
            param.put("start", query.getDate().get(0));
            param.put("end", query.getDate().get(1));
        }
        if(query.getCanOffset()){
            dataSql.append(" and offset_total <> total ");
        }
        StringBuffer countSql = new StringBuffer("select COUNT(1) COUNT from (").append(dataSql).append(") as temp");
        log.info("countSql = {}, parameters = {}", countSql, JSONObject.toJSONString(param.build()));
        Integer count = jdbcTemplate.queryForObject(countSql.toString(), param.build(), Integer.class);
        if(count == 0){
            pageList.setTotal(0);
            return pageList;
        }
        String limit = String.format(" limit %s,%s", (query.getPageIndex() - 1) * query.getPageSize(), query.getPageSize());
        dataSql.append(query.getOrderBy()).append(limit);
        log.info("dataSql = {}, parameters = {}", dataSql, JSONObject.toJSONString(param.build()));
        List<InvoiceApply> invoiceApplyList = JdbcTemplateUtils.queryForPOJOList(dataSql.toString(), param.build(), ModelConst.T_INVOICE_APPLY, InvoiceApply.class);
        pageList.setTotal(count);
        pageList.setData(invoiceApplyList);
        return pageList;
    }

    /**
     * 递归拆分发票明细（自动拆票）
     *
     * @param invoiceApplyProcess         参与拆分的申请
     * @param invoiceProcessBatchDataList 已经拆分好的有效申请
     * @param maxInvoice                  发票最大金额
     * @param originalInvoiceApplyProcess 最初申请金额（用来解决尾差问题）
     */
    public void recursionProcessInvoiceDetailProcess(InvoiceApplyForm invoiceApplyProcess, List<InvoiceApplyForm> invoiceProcessBatchDataList,
                                                     BigDecimal maxInvoice, InvoiceApplyForm originalInvoiceApplyProcess) {
        List<ApplyInvoiceDetailDto> invoiceDetailProcessList = invoiceApplyProcess.getApplyInvoiceDetailDtoList();
        Assert.isTrue(!CollectionUtils.isEmpty(invoiceDetailProcessList), "拆分发票明细参数不能为空，不能继续执行自动拆分");

        List<ApplyInvoiceDetailDto> amountNullCollect = invoiceDetailProcessList.stream().filter(t -> t.getAmount() == null).collect(Collectors.toList());
        Assert.isTrue(CollectionUtils.isEmpty(amountNullCollect), "发票明细未税金额不能为空");

        //未税合计总和
        BigDecimal invoiceDetailProcessAmountSum = CalculatorUtils.sum(invoiceDetailProcessList, applyInvoiceDetailAmountFun);
        if (invoiceDetailProcessAmountSum.compareTo(maxInvoice) >= 0) {
            //记录下一次参与拆分的明细
            List<ApplyInvoiceDetailDto> invoiceProcessDetailNextList = new ArrayList<>();

            //本次申请的未税合计
            BigDecimal invoiceBatchAmount = BigDecimal.ZERO;
            //本次申请的明细
            List<ApplyInvoiceDetailDto> invoiceProcessDetailBatchList = new ArrayList<>();
            for (int i = 0; i < invoiceDetailProcessList.size(); i++) {
                ApplyInvoiceDetailDto invoiceDetailProcess = invoiceDetailProcessList.get(i);
                /*
                 * 原则上，个数尽量是整数
                 * 如果未税申请金额小于10万，则尝试+下一个A的金额，如果结果不超过10万，则继续下一个，超过了则减去A，
                 * 减去A之后，拆分A的数量，取最接近的，如果单价超过10万的，则取小数的数量，暂时小数位6位小数
                 */
                if (invoiceBatchAmount.compareTo(maxInvoice) < 0) {
                    BigDecimal invoiceDetailProcessAmount = invoiceDetailProcess.getAmount();
                    //加上本次的未税金额
                    invoiceBatchAmount = BigDecimalUtils.add(invoiceBatchAmount, invoiceDetailProcessAmount);
                    if (invoiceBatchAmount.doubleValue() >= maxInvoice.doubleValue()) {
                        //退回到上一次增加的金额
                        invoiceBatchAmount = BigDecimalUtils.sub(invoiceBatchAmount, invoiceDetailProcessAmount);

                        //计算到10万的差额
                        BigDecimal maxInvoiceSub = BigDecimalUtils.sub(maxInvoice, invoiceBatchAmount);
                        //不能等于最大可申请金额，所以把差额减少(货物发票减1元，服务费减0.1元)
                        if (maxInvoice.doubleValue() > MAX_INVOICE.doubleValue()) {
                            maxInvoiceSub = BigDecimalUtils.sub(maxInvoiceSub, BigDecimal.ONE);
                        } else {
                            maxInvoiceSub = BigDecimalUtils.sub(maxInvoiceSub, new BigDecimal("0.1"));
                        }

                        //含税税单价
                        BigDecimal price = invoiceDetailProcess.getPrice();
                        //未税单价
                        BigDecimal pretaxPrice = invoiceDetailProcess.getPretax_price();

                        //暂定为普通发票大于100元、服务费发票大于1元的继续，小于100元再拆分，数量可能会很小，就计入下一张票了
                        int value = 100;
                        if (maxInvoice.doubleValue() < MAX_INVOICE.doubleValue()) {
                            //服务费可以开比较小的金额，暂时定为服务费大于1块钱的继续
                            value = 1;
                        }

                        if (maxInvoiceSub.doubleValue() > value) {
                            BigDecimal invoiceNum;
                            //如果能保证拆分的数量为整数
                            if (maxInvoiceSub.doubleValue() >= price.doubleValue()) {
                                //计算申请数量，向下取整，取正数去申请
                                invoiceNum = BigDecimalUtils.div(maxInvoiceSub, price).setScale(0, RoundingMode.FLOOR);
                            } else {
                                invoiceNum = BigDecimalUtils.round(BigDecimalUtils.div(maxInvoiceSub, price), 6);
                            }

                            //整理新的申请数据，只要赋值Total和Quantity
                            ApplyInvoiceDetailDto invoiceDetailProcessNew = new ApplyInvoiceDetailDto();
                            BeanUtils.copyProperties(invoiceDetailProcess, invoiceDetailProcessNew);
                            BigDecimal total = BigDecimalUtils.round(BigDecimalUtils.mul(price, invoiceNum), 2);
                            BigDecimal amount = BigDecimalUtils.round(BigDecimalUtils.mul(pretaxPrice, invoiceNum), 2);
                            BigDecimal tax = BigDecimalUtils.sub(total, amount);
                            invoiceDetailProcessNew
                                    .setTotal(total)
                                    .setAmount(amount)
                                    .setTax(tax)
                                    .setQuantity(invoiceNum);
                            invoiceProcessDetailBatchList.add(invoiceDetailProcessNew);

                            //剩下的数量
                            BigDecimal remainingQuantity = BigDecimalUtils.sub(invoiceDetailProcess.getQuantity(), invoiceNum);
                            //剩下的未税明细放入集合，继续申请
                            BigDecimal remainingAmount = BigDecimalUtils.round(BigDecimalUtils.mul(pretaxPrice, remainingQuantity), 2);
                            //剩下的含税明细放入集合，继续申请
                            BigDecimal remainingGrossAmount = BigDecimalUtils.round(BigDecimalUtils.mul(price, remainingQuantity), 2);

                            if (remainingQuantity.compareTo(BigDecimal.ZERO) > 0 && remainingAmount.compareTo(BigDecimal.ZERO) > 0) {
                                ApplyInvoiceDetailDto invoiceDetailProcessNext = new ApplyInvoiceDetailDto();
                                BeanUtils.copyProperties(invoiceDetailProcess, invoiceDetailProcessNext);
                                invoiceDetailProcessNext
                                        .setTotal(remainingGrossAmount)
                                        .setAmount(remainingAmount)
                                        .setQuantity(remainingQuantity);
                                invoiceProcessDetailNextList.add(invoiceDetailProcessNext);
                            }
                        } else {
                            //暂定为小于100元的，剩下明细放入集合，继续申请
                            BigDecimal remainingQuantity = BigDecimalUtils.round(BigDecimalUtils.div(maxInvoiceSub, price), 2);
                            invoiceDetailProcess
                                    .setTotal(maxInvoiceSub)
                                    .setAmount(maxInvoiceSub)
                                    .setQuantity(remainingQuantity);
                            invoiceProcessDetailNextList.add(invoiceDetailProcess);
                        }

                        //本次申请结束，将剩余数据继续申请
                        for (int j = i + 1; j < invoiceDetailProcessList.size(); j++) {
                            invoiceProcessDetailNextList.add(invoiceDetailProcessList.get(j));
                        }
                        break;
                    } else {
                        invoiceProcessDetailBatchList.add(invoiceDetailProcess);
                    }
                }
            }
            //下次的数据
            invoiceApplyProcess.setApplyInvoiceDetailDtoList(invoiceProcessDetailNextList);
            //已经处理好的数据
            InvoiceApplyForm invoiceApplyProcessData = new InvoiceApplyForm();
            BeanUtils.copyProperties(invoiceApplyProcess, invoiceApplyProcessData);
            invoiceApplyProcessData.setApplyInvoiceDetailDtoList(invoiceProcessDetailBatchList);
            invoiceProcessBatchDataList.add(invoiceApplyProcessData);
            //没有拆分完成，继续拆分
            if (!CollectionUtils.isEmpty(invoiceProcessDetailNextList)) {
                this.recursionProcessInvoiceDetailProcess(invoiceApplyProcess, invoiceProcessBatchDataList, maxInvoice, originalInvoiceApplyProcess);
            }
        } else {
            //不到最大金额或者最后一次
            invoiceProcessBatchDataList.add(invoiceApplyProcess);

            List<ApplyInvoiceDetailDto> originalInvoiceDetailProcess = originalInvoiceApplyProcess.getApplyInvoiceDetailDtoList();
            Assert.isTrue(!CollectionUtils.isEmpty(originalInvoiceDetailProcess), "拆分发票明细参数不能为空，不能继续执行自动拆分");

            //对拆分好的发票进行未税总额验证
            BigDecimal originalAmountSum = CalculatorUtils.sum(originalInvoiceDetailProcess, applyInvoiceDetailAmountFun);
            //对拆分好的发票进行未税总额验证
            BigDecimal originalTotalSum = CalculatorUtils.sum(originalInvoiceDetailProcess, applyInvoiceDetailTotalFun);

            BigDecimal batchAmountTotal = BigDecimal.ZERO;
            BigDecimal batchTotalTotal = BigDecimal.ZERO;
            for (InvoiceApplyForm invoiceApplyProce : invoiceProcessBatchDataList) {
                List<ApplyInvoiceDetailDto> invoiceDetailProcess = invoiceApplyProce.getApplyInvoiceDetailDtoList();
                BigDecimal batchAmountSum = CalculatorUtils.sum(invoiceDetailProcess, applyInvoiceDetailAmountFun);
                BigDecimal batchTotalSum = CalculatorUtils.sum(invoiceDetailProcess, applyInvoiceDetailTotalFun);
                batchAmountTotal = BigDecimalUtils.add(batchAmountTotal, batchAmountSum);
                batchTotalTotal = BigDecimalUtils.add(batchTotalTotal, batchTotalSum);
            }
            //尾差
            BigDecimal amountSub = BigDecimalUtils.sub(originalAmountSum, batchAmountTotal);
            BigDecimal totalSub = BigDecimalUtils.sub(originalTotalSum, batchTotalTotal);
            //不相等的话，需要做处理，找第一个可以增加尾差数并且不到最大金额的发票的第一条进行补尾差
            if (amountSub.compareTo(BigDecimal.ZERO) != 0) {
                for (InvoiceApplyForm invoiceApplyProce : invoiceProcessBatchDataList) {
                    List<ApplyInvoiceDetailDto> invoiceDetailProcesses = invoiceApplyProce.getApplyInvoiceDetailDtoList();
                    BigDecimal batchAmountSum = CalculatorUtils.sum(invoiceDetailProcesses, applyInvoiceDetailAmountFun);
                    //如果未税总额+尾差金额可以开票
                    if (BigDecimalUtils.add(batchAmountSum, amountSub).compareTo(maxInvoice) < 0) {
                        //用第一条发票明细（尾差金额+原商品的未税总额）/个数，调整单价
                        ApplyInvoiceDetailDto invoiceDetailProcess = invoiceDetailProcesses.get(0);
                        BigDecimal amount = invoiceDetailProcess.getAmount();
                        BigDecimal total = invoiceDetailProcess.getTotal();

                        Assert.notNull(amount, "补尾差时交易" + invoiceDetailProcess.getId() + "的未税金额异常");
                        amount = BigDecimalUtils.add(amount, amountSub);
                        total = BigDecimalUtils.add(total, totalSub);
                        BigDecimal price = BigDecimalUtils.div(amount, invoiceDetailProcess.getQuantity());

                        invoiceDetailProcess.setAmount(amount).setPrice(price).setTotal(total);
                        invoiceDetailProcesses.set(0, invoiceDetailProcess);
                        invoiceApplyProce.setApplyInvoiceDetailDtoList(invoiceDetailProcesses);
                        break;
                    }
                }

            }

        }
    }

    /**
     * 电子普通发票申请开票自动拆分明细
     * 明细个数最多100个
     *
     * @param processes
     * @return
     */
    public List<InvoiceApplyForm> splitInvoiceDetailProcessCount(List<InvoiceApplyForm> processes) {
        //只对南阳固买并且是电子普通发票的做拆分
        Set<InvoiceApplyForm> invoiceApplyProcessSet = processes.stream().filter(t -> InvoiceType.valueOf(t.getType()) == InvoiceType.VatOrdinaryElectronic
                && t.getSale_organization_id().getId().equals(PlatformConst.PLATFORM_ORGANIZATION_IDS.get(1))).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(invoiceApplyProcessSet)) {
            return processes;
        }

        int invoiceCount = 100;
        List<InvoiceApplyForm> invoiceApplyProcessNewList = new ArrayList<>();
        for (InvoiceApplyForm invoiceApplyProcess : processes) {
            List<ApplyInvoiceDetailDto> invoiceDetailProcessList = invoiceApplyProcess.getApplyInvoiceDetailDtoList();
            if (CollectionUtils.isEmpty(invoiceDetailProcessList) || invoiceDetailProcessList.size() < 100) {
                invoiceApplyProcessNewList.add(invoiceApplyProcess);
                continue;
            }

            //总个数
            int count = invoiceDetailProcessList.size();
            //拆分的集合数
            int size = BigDecimalUtils.div(new BigDecimal(count), new BigDecimal(invoiceCount)).setScale(0,RoundingMode.CEILING).intValue();
            for (int i = 0; i < size; i++) {
                InvoiceApplyForm invoiceApplyProcessNew = new InvoiceApplyForm();
                List<ApplyInvoiceDetailDto> invoiceDetailProcessNewList = new ArrayList<>();
                BeanUtils.copyProperties(invoiceApplyProcess, invoiceApplyProcessNew);

                for (int in = 0; in < invoiceCount; in++) {
                    int index = invoiceCount * i + in;
                    if (index >= count) {
                        break;
                    }

                    invoiceDetailProcessNewList.add(invoiceDetailProcessList.get(index));
                }

                invoiceApplyProcessNew.setApplyInvoiceDetailDtoList(invoiceDetailProcessNewList);
                invoiceApplyProcessNewList.add(invoiceApplyProcessNew);
            }
        }

        return invoiceApplyProcessNewList;
    }

    @Override
    public void verifyCreate(InvoiceApplyForm invoiceApplyForm)  {

        //必填验证
        Assert.notNull(invoiceApplyForm.getType(), "发票类型不能为空");
        Assert.notNull(invoiceApplyForm.getBusiness_type(), "发票业务类型不能为空");
        Assert.notNull(invoiceApplyForm.getPurchase_name(), "票面购买方名称不能为空");
        Assert.notNull(invoiceApplyForm.getPurchase_number(), "票面购买方纳税人识别号不能为空");
        Assert.notNull(invoiceApplyForm.getPurchase_address(), "发票地址不能为空");
        Assert.notNull(invoiceApplyForm.getPurchase_phone(), "开票电话不能为空");
        Assert.notNull(invoiceApplyForm.getPurchase_bank(), "开户行不能为空");
        Assert.notNull(invoiceApplyForm.getPurchase_account(), "银行账号不能为空");
        Assert.notNull(invoiceApplyForm.getReceiver(), "收件人不能为空");
        Assert.notNull(invoiceApplyForm.getPhone(), "收票人电话不能为空");
        Assert.notNull(invoiceApplyForm.getAddress(), "收票地址不能为空");
        Assert.notNull(invoiceApplyForm.getApply_member_id(), "申请人id不能为空");
        Assert.isTrue(StringUtils.isNotBlank(invoiceApplyForm.getApply_member_name()), "申请人不能为空");
        Assert.notNull(invoiceApplyForm.getPurchase_organization_id(), "购买方组织id不能为空");
        Assert.isTrue(StringUtils.isNotBlank(invoiceApplyForm.getPurchase_organization_name()), "购买方组织全称不能为空");
        Assert.notNull(invoiceApplyForm.getSale_organization_id(), "销售方组织id不能为空");
        Assert.isTrue(StringUtils.isNotBlank(invoiceApplyForm.getSale_organization_name()), "销售方组织全称不能为空");
        Assert.notNull(invoiceApplyForm.getTrade_order_type(), "交易订单类型不能为空");
    }

    @Override
    public SingleResponse approveInvoice(InvoiceApprove approve) {
        InvoiceApply invoice = JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY).selectField(" * ")
                .eq("id", approve.getId())
                .eq("statuss", InvoiceStatus.Approving.name())
                .queryForPOJO(InvoiceApply.class);
        if(invoice == null){
            return SingleResponse.buildFailure("0", "不存在待审批的发票申请单");
        }
        //生成实际开票数据
        if (!InvoiceBusinessType.Product.name().equals(invoice.getBusiness_type())) {
            return SingleResponse.buildFailure("0", "发票业务类型只能是普通发票和服务费普通发票");
        }
        if (!InvoiceStatus.Returned.name().equals(approve.getStatus()) && !InvoiceStatus.DrawerConfirming.name().equals(approve.getStatus())) {
            return SingleResponse.buildFailure("0", "审批状态只能是已退回或待开票");
        }
        InvoiceExtendsAttr extendsAttr = InvoiceExtendsAttr.conversion(invoice.getExtends_attr()).get();
        extendsAttr.setApprovalReason(approve.getApprovalReason());
        invoice.setStatuss(approve.getStatus())
                .setExtends_attr(InvoiceExtendsAttr.conversion(extendsAttr).get());

        invoice.setGolden_tax_id(goldenTaxService.generateGoldenTax(invoice));
        JdbcTemplateUtils.builder(ModelConst.T_INVOICE_APPLY)
                .putFieldAndVal("statuss", invoice.getStatuss())
                .putFieldAndVal("extends_attr", invoice.getExtends_attr())
                .putFieldAndVal("golden_tax_id", invoice.getGolden_tax_id())
                .eq("id", invoice.getId())
                .update();
        return SingleResponse.buildSuccess();
    }
}
